From 37e4ae59a2b83f764a08b113b77ab2ddae3be6c9 Mon Sep 17 00:00:00 2001 From: spamegg Date: Mon, 8 Apr 2024 20:37:52 +0300 Subject: [PATCH] ch06 refactored common parts, deduped code --- src/main/scala/ch06/asyncHttp/main.scala | 119 ++++------------------- src/main/scala/ch06/asyncTcp/main.scala | 96 +----------------- src/main/scala/ch06/common/common.scala | 102 +++++++++++++++++++ 3 files changed, 126 insertions(+), 191 deletions(-) create mode 100644 src/main/scala/ch06/common/common.scala diff --git a/src/main/scala/ch06/asyncHttp/main.scala b/src/main/scala/ch06/asyncHttp/main.scala index ca1af08..d85ff3b 100644 --- a/src/main/scala/ch06/asyncHttp/main.scala +++ b/src/main/scala/ch06/asyncHttp/main.scala @@ -11,8 +11,6 @@ import LibUV.*, LibUVConstants.* import HTTP.RequestHandler import ch03.http.{HttpRequest, HttpResponse} -type ClientState = CStruct3[Ptr[Byte], CSize, CSize] - @main def asyncHttp(args: String): Unit = serveHttp( @@ -28,6 +26,25 @@ def serveHttp(port: Int, handler: RequestHandler): Unit = this.router = handler serveTcp(c"0.0.0.0", port, 0, 4096, connectionCB) +// cannot be factored out due to readCB differences. +val connectionCB = CFuncPtr2.fromScalaFunction[TCPHandle, Int, Unit]: + (handle: TCPHandle, status: Int) => + println("received connection") + + // initialize the new client tcp handle and its state + val client = malloc(uv_handle_size(UV_TCP_T)).asInstanceOf[TCPHandle] + checkError(uv_tcp_init(loop, client), "uv_tcp_init(client)") + + var clientStatePtr = (!client).asInstanceOf[Ptr[ClientState]] + clientStatePtr = initializeClientState(client) + + // accept the incoming connection into the new handle + checkError(uv_accept(handle, client), "uv_accept") + + // set up callbacks for incoming data + checkError(uv_read_start(client, allocCB, readCB), "uv_read_start") + +// cannot be factored out due to differences. val readCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSSize, Ptr[Buffer], Unit]: (client: TCPHandle, size: CSSize, buffer: Ptr[Buffer]) => if size < 0 then shutdown(client) @@ -42,21 +59,17 @@ val readCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSSize, Ptr[Buffer], Unit]: println(s"error during parsing: ${e}") shutdown(client) +// cannot be factored out due to differences. def sendResponse(client: TCPHandle, response: HttpResponse): Unit = val req = malloc(uv_req_size(UV_WRITE_REQ_T)).asInstanceOf[WriteReq] val responseBuffer = malloc(sizeof[Buffer]).asInstanceOf[Ptr[Buffer]] - responseBuffer._1 = malloc(4096) // 0.5 responseBuffer._2 = 4096.toUSize // 0.5 - HTTP.makeResponse(response, responseBuffer) responseBuffer._2 = string.strlen(responseBuffer._1) - !req = responseBuffer.asInstanceOf[Ptr[Byte]] checkError(uv_write(req, client, responseBuffer, 1, writeCB), "uv_write") -val loop = uv_default_loop() - def serveTcp( address: CString, port: Int, @@ -69,99 +82,9 @@ def serveTcp( println(s"uv_ip4_addr returned $addrConvert") val handle = malloc(uv_handle_size(UV_TCP_T)).asInstanceOf[TCPHandle] + checkError(uv_tcp_init(loop, handle), "uv_tcp_init(server)") checkError(uv_tcp_bind(handle, addr, flags), "uv_tcp_bind") checkError(uv_listen(handle, backlog, callback), "uv_tcp_listen") uv_run(loop, UV_RUN_DEFAULT) - -val connectionCB = CFuncPtr2.fromScalaFunction[TCPHandle, Int, Unit]: - (handle: TCPHandle, status: Int) => - println("received connection") - - // initialize the new client tcp handle and its state - val client = malloc(uv_handle_size(UV_TCP_T)).asInstanceOf[TCPHandle] - checkError(uv_tcp_init(loop, client), "uv_tcp_init(client)") - - var clientStatePtr = (!client).asInstanceOf[Ptr[ClientState]] - clientStatePtr = initializeClientState(client) - - // accept the incoming connection into the new handle - checkError(uv_accept(handle, client), "uv_accept") - - // set up callbacks for incoming data - checkError(uv_read_start(client, allocCB, readCB), "uv_read_start") - -def initializeClientState(client: TCPHandle): Ptr[ClientState] = - val clientStatePtr = malloc(sizeof[ClientState]).asInstanceOf[Ptr[ClientState]] - - stdio.printf( - c"allocated data at %x; assigning into handle storage at %x\n", - clientStatePtr, - client - ) - - val clientStateData = malloc(4096) // 0.5 - clientStatePtr._1 = clientStateData - clientStatePtr._2 = 4096.toUSize // total // 0.5 - clientStatePtr._3 = 0.toUSize // used // 0.5 - - !client = clientStatePtr.asInstanceOf[Ptr[Byte]] - clientStatePtr - -val allocCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSize, Ptr[Buffer], Unit]: - (client: TCPHandle, size: CSize, buffer: Ptr[Buffer]) => - println("allocating 4096 bytes") - val buf = malloc(4096) // 0.5 - buffer._1 = buf - buffer._2 = 4096.toUSize // 0.5 - -def appendData(state: Ptr[ClientState], size: CSSize, buffer: Ptr[Buffer]): Unit = - val copyPosition = state._1 + state._3 - string.strncpy(copyPosition, buffer._1, size.toUSize) // 0.5 - - // be sure to update the length of the data since we have copied into it - state._3 = state._3 + size.toUSize // 0.5 - - stdio.printf(c"client %x: %d/%d bytes used\n", state, state._3, state._2) - -def sendResponse(client: TCPHandle, state: Ptr[ClientState]): Unit = - val resp = malloc(uv_req_size(UV_WRITE_REQ_T)).asInstanceOf[WriteReq] - val responseBuffer = malloc(sizeof[Buffer]).asInstanceOf[Ptr[Buffer]] - responseBuffer._1 = makeResponse(state) - responseBuffer._2 = string.strlen(responseBuffer._1) - !resp = responseBuffer.asInstanceOf[Ptr[Byte]] - checkError(uv_write(resp, client, responseBuffer, 1, writeCB), "uv_write") - -def makeResponse(state: Ptr[ClientState]): CString = - val responseFormat = c"received response:\n%s\n" - val responseData = malloc(string.strlen(responseFormat) + state._3) - stdio.sprintf(responseData, responseFormat, state._1) - responseData - -val writeCB = CFuncPtr2.fromScalaFunction[WriteReq, Int, Unit]: - (writeReq: WriteReq, status: Int) => - println("write completed") - val responseBuffer = (!writeReq).asInstanceOf[Ptr[Buffer]] - stdlib.free(responseBuffer._1) - stdlib.free(responseBuffer.asInstanceOf[Ptr[Byte]]) - stdlib.free(writeReq.asInstanceOf[Ptr[Byte]]) - -def shutdown(client: TCPHandle): Unit = - val shutdownReq = malloc(uv_req_size(UV_SHUTDOWN_REQ_T)).asInstanceOf[ShutdownReq] - !shutdownReq = client.asInstanceOf[Ptr[Byte]] - checkError(uv_shutdown(shutdownReq, client, shutdownCB), "uv_shutdown") - -val shutdownCB = CFuncPtr2.fromScalaFunction[ShutdownReq, Int, Unit]: - (shutdownReq: ShutdownReq, status: Int) => - println("all pending writes complete, closing TCP connection") - val client = (!shutdownReq).asInstanceOf[TCPHandle] - checkError(uv_close(client, closeCB), "uv_close") - stdlib.free(shutdownReq.asInstanceOf[Ptr[Byte]]) - -val closeCB = CFuncPtr1.fromScalaFunction[TCPHandle, Unit]: (client: TCPHandle) => - println("closed client connection") - val clientStatePtr = (!client).asInstanceOf[Ptr[ClientState]] - stdlib.free(clientStatePtr._1) - stdlib.free(clientStatePtr.asInstanceOf[Ptr[Byte]]) - stdlib.free(client.asInstanceOf[Ptr[Byte]]) diff --git a/src/main/scala/ch06/asyncTcp/main.scala b/src/main/scala/ch06/asyncTcp/main.scala index 033f5da..3d7dc8c 100644 --- a/src/main/scala/ch06/asyncTcp/main.scala +++ b/src/main/scala/ch06/asyncTcp/main.scala @@ -9,35 +9,13 @@ import stdlib.malloc import LibUV.*, LibUVConstants.* -type ClientState = CStruct3[Ptr[Byte], CSize, CSize] - @main def asyncTcp: Unit = println("hello!") serveTcp(c"0.0.0.0", 8080, 0, 100, connectionCB) println("done?") -val loop = uv_default_loop() - -def serveTcp( - address: CString, - port: Int, - flags: Int, - backlog: Int, - callback: ConnectionCB -): Unit = - val addr = stackalloc[Byte](1) - val addrConvert = uv_ip4_addr(address, port, addr) - println(s"uv_ip4_addr returned $addrConvert") - - val handle = malloc(uv_handle_size(UV_TCP_T)).asInstanceOf[TCPHandle] - - checkError(uv_tcp_init(loop, handle), "uv_tcp_init(server)") - checkError(uv_tcp_bind(handle, addr, flags), "uv_tcp_bind") - checkError(uv_listen(handle, backlog, callback), "uv_tcp_listen") - - uv_run(loop, UV_RUN_DEFAULT) - +// cannot be factored out due to readCB differences. val connectionCB = CFuncPtr2.fromScalaFunction[TCPHandle, Int, Unit]: (handle: TCPHandle, status: Int) => println("received connection") @@ -47,7 +25,7 @@ val connectionCB = CFuncPtr2.fromScalaFunction[TCPHandle, Int, Unit]: checkError(uv_tcp_init(loop, client), "uv_tcp_init(client)") var clientStatePtr = (!client).asInstanceOf[Ptr[ClientState]] - clientStatePtr = initialize_client_state(client) + clientStatePtr = initializeClientState(client) // accept the incoming connection into the new handle checkError(uv_accept(handle, client), "uv_accept") @@ -55,27 +33,7 @@ val connectionCB = CFuncPtr2.fromScalaFunction[TCPHandle, Int, Unit]: // set up callbacks for incoming data checkError(uv_read_start(client, allocCB, readCB), "uv_read_start") -def initialize_client_state(client: TCPHandle): Ptr[ClientState] = - val clientStatePtr = stdlib.malloc(sizeof[ClientState]).asInstanceOf[Ptr[ClientState]] - stdio.printf( - c"allocated data at %x; assigning into handle storage at %x\n", - clientStatePtr, - client - ) - val client_state_data = stdlib.malloc(4096.toUSize) // 0.5 - clientStatePtr._1 = client_state_data - clientStatePtr._2 = 4096.toUSize // total // 0.5 - clientStatePtr._3 = 0.toUSize // used // 0.5 - !client = clientStatePtr.asInstanceOf[Ptr[Byte]] - clientStatePtr - -val allocCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSize, Ptr[Buffer], Unit]: - (client: TCPHandle, size: CSize, buffer: Ptr[Buffer]) => - println("allocating 4096 bytes") - val buf = stdlib.malloc(4096.toUSize) // 0.5 - buffer._1 = buf - buffer._2 = 4096.toUSize // 0.5 - +// cannot be factored out due to differences. val readCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSSize, Ptr[Buffer], Unit]: (client: TCPHandle, size: CSSize, buffer: Ptr[Buffer]) => println(s"read $size bytes") @@ -88,51 +46,3 @@ val readCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSSize, Ptr[Buffer], Unit]: else appendData(clientStatePtr, size, buffer) stdlib.free(buffer._1) - -def appendData(state: Ptr[ClientState], size: CSSize, buffer: Ptr[Buffer]): Unit = - val copy_position = state._1 + state._3 - string.strncpy(copy_position, buffer._1, size.toUSize) // 0.5 - // be sure to update the length of the data since we have copied into it - state._3 = state._3 + size.toUSize // 0.5 - stdio.printf(c"client %x: %d/%d bytes used\n", state, state._3, state._2) - -def sendResponse(client: TCPHandle, state: Ptr[ClientState]): Unit = - val resp = malloc(uv_req_size(UV_WRITE_REQ_T)).asInstanceOf[WriteReq] - val resp_buffer = malloc(sizeof[Buffer]).asInstanceOf[Ptr[Buffer]] - resp_buffer._1 = make_response(state) - resp_buffer._2 = string.strlen(resp_buffer._1) - !resp = resp_buffer.asInstanceOf[Ptr[Byte]] - checkError(uv_write(resp, client, resp_buffer, 1, writeCB), "uv_write") - -def make_response(state: Ptr[ClientState]): CString = - val response_format = c"received response:\n%s\n" - val response_data = malloc(string.strlen(response_format) + state._3) - stdio.sprintf(response_data, response_format, state._1) - response_data - -val writeCB = CFuncPtr2.fromScalaFunction[WriteReq, Int, Unit]: - (writeReq: WriteReq, status: Int) => - println("write completed") - val resp_buffer = (!writeReq).asInstanceOf[Ptr[Buffer]] - stdlib.free(resp_buffer._1) - stdlib.free(resp_buffer.asInstanceOf[Ptr[Byte]]) - stdlib.free(writeReq.asInstanceOf[Ptr[Byte]]) - -def shutdown(client: TCPHandle): Unit = - val shutdownReq = malloc(uv_req_size(UV_SHUTDOWN_REQ_T)).asInstanceOf[ShutdownReq] - !shutdownReq = client.asInstanceOf[Ptr[Byte]] - checkError(uv_shutdown(shutdownReq, client, shutdownCB), "uv_shutdown") - -val shutdownCB = CFuncPtr2.fromScalaFunction[ShutdownReq, Int, Unit]: - (shutdownReq: ShutdownReq, status: Int) => - println("all pending writes complete, closing TCP connection") - val client = (!shutdownReq).asInstanceOf[TCPHandle] - checkError(uv_close(client, closeCB), "uv_close") - stdlib.free(shutdownReq.asInstanceOf[Ptr[Byte]]) - -val closeCB = CFuncPtr1.fromScalaFunction[TCPHandle, Unit]: (client: TCPHandle) => - println("closed client connection") - val clientStatePtr = (!client).asInstanceOf[Ptr[ClientState]] - stdlib.free(clientStatePtr._1) - stdlib.free(clientStatePtr.asInstanceOf[Ptr[Byte]]) - stdlib.free(client.asInstanceOf[Ptr[Byte]]) diff --git a/src/main/scala/ch06/common/common.scala b/src/main/scala/ch06/common/common.scala new file mode 100644 index 0000000..42c5f62 --- /dev/null +++ b/src/main/scala/ch06/common/common.scala @@ -0,0 +1,102 @@ +package ch06 + +import scalanative.unsigned.UnsignedRichInt +import scalanative.unsafe.{Ptr, CString, CSize, CSSize, stackalloc, sizeof, CQuote} +import scalanative.unsafe.{CStruct3, CFuncPtr1, CFuncPtr2, CFuncPtr3} +import scalanative.libc.{stdio, stdlib, string}, stdlib.malloc, string.strlen +import LibUV.* +import LibUVConstants.* + +type ClientState = CStruct3[Ptr[Byte], CSize, CSize] + +val loop = uv_default_loop() + +def serveTcp( + address: CString, + port: Int, + flags: Int, + backlog: Int, + callback: ConnectionCB +): Unit = + val addr = stackalloc[Byte](1) + val addrConvert = uv_ip4_addr(address, port, addr) + println(s"uv_ip4_addr returned $addrConvert") + + val handle = malloc(uv_handle_size(UV_TCP_T)).asInstanceOf[TCPHandle] + + checkError(uv_tcp_init(loop, handle), "uv_tcp_init(server)") + checkError(uv_tcp_bind(handle, addr, flags), "uv_tcp_bind") + checkError(uv_listen(handle, backlog, callback), "uv_tcp_listen") + + uv_run(loop, UV_RUN_DEFAULT) + +def initializeClientState(client: TCPHandle): Ptr[ClientState] = + val clientStatePtr = malloc(sizeof[ClientState]).asInstanceOf[Ptr[ClientState]] + + stdio.printf( + c"allocated data at %x; assigning into handle storage at %x\n", + clientStatePtr, + client + ) + + val clientStateData = malloc(4096) // 0.5 + clientStatePtr._1 = clientStateData + clientStatePtr._2 = 4096.toUSize // total // 0.5 + clientStatePtr._3 = 0.toUSize // used // 0.5 + !client = clientStatePtr.asInstanceOf[Ptr[Byte]] + clientStatePtr + +val allocCB = CFuncPtr3.fromScalaFunction[TCPHandle, CSize, Ptr[Buffer], Unit]: + (client: TCPHandle, size: CSize, buffer: Ptr[Buffer]) => + println("allocating 4096 bytes") + val buf = malloc(4096) // 0.5 + buffer._1 = buf + buffer._2 = 4096.toUSize // 0.5 + +val writeCB = CFuncPtr2.fromScalaFunction[WriteReq, Int, Unit]: + (writeReq: WriteReq, status: Int) => + println("write completed") + val responseBuffer = (!writeReq).asInstanceOf[Ptr[Buffer]] + stdlib.free(responseBuffer._1) + stdlib.free(responseBuffer.asInstanceOf[Ptr[Byte]]) + stdlib.free(writeReq.asInstanceOf[Ptr[Byte]]) + +def shutdown(client: TCPHandle): Unit = + val shutdownReq = malloc(uv_req_size(UV_SHUTDOWN_REQ_T)).asInstanceOf[ShutdownReq] + !shutdownReq = client.asInstanceOf[Ptr[Byte]] + checkError(uv_shutdown(shutdownReq, client, shutdownCB), "uv_shutdown") + +val shutdownCB = CFuncPtr2.fromScalaFunction[ShutdownReq, Int, Unit]: + (shutdownReq: ShutdownReq, status: Int) => + println("all pending writes complete, closing TCP connection") + val client = (!shutdownReq).asInstanceOf[TCPHandle] + checkError(uv_close(client, closeCB), "uv_close") + stdlib.free(shutdownReq.asInstanceOf[Ptr[Byte]]) + +val closeCB = CFuncPtr1.fromScalaFunction[TCPHandle, Unit]: (client: TCPHandle) => + println("closed client connection") + val clientStatePtr = (!client).asInstanceOf[Ptr[ClientState]] + stdlib.free(clientStatePtr._1) + stdlib.free(clientStatePtr.asInstanceOf[Ptr[Byte]]) + stdlib.free(client.asInstanceOf[Ptr[Byte]]) + +def makeResponse(state: Ptr[ClientState]): CString = + val response_format = c"received response:\n%s\n" + val response_data = malloc(strlen(response_format) + state._3) + stdio.sprintf(response_data, response_format, state._1) + response_data + +def sendResponse(client: TCPHandle, state: Ptr[ClientState]): Unit = + val req = malloc(uv_req_size(UV_WRITE_REQ_T)).asInstanceOf[WriteReq] + val responseBuffer = malloc(sizeof[Buffer]).asInstanceOf[Ptr[Buffer]] + responseBuffer._1 = makeResponse(state) + responseBuffer._2 = string.strlen(responseBuffer._1) + !req = responseBuffer.asInstanceOf[Ptr[Byte]] + checkError(uv_write(req, client, responseBuffer, 1, writeCB), "uv_write") + +def appendData(state: Ptr[ClientState], size: CSSize, buffer: Ptr[Buffer]): Unit = + val copyPosition = state._1 + state._3 + string.strncpy(copyPosition, buffer._1, size.toUSize) // 0.5 + // be sure to update the length of the data since we have copied into it + state._3 = state._3 + size.toUSize // 0.5 + stdio.printf(c"client %x: %d/%d bytes used\n", state, state._3, state._2)