From efb8de3e692d8f788fd08cca5a0409fc0692c32a Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Tue, 26 Dec 2023 13:57:17 +1100 Subject: [PATCH] Optimise http client to avoid unnecessary memcpy when reading most of body/chunk response data --- src/Http_Worker.c | 85 ++++++++++++++++++++++++----------------------- src/Platform.h | 2 +- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/Http_Worker.c b/src/Http_Worker.c index 05270c2fe..5234d34b9 100644 --- a/src/Http_Worker.c +++ b/src/Http_Worker.c @@ -558,6 +558,7 @@ static cc_result HttpConnection_Open(struct HttpConnection* conn, const struct H if ((res = Socket_ParseAddress(&host, portNum, addrs, &numValidAddrs))) return res; res = ERR_INVALID_ARGUMENT; /* in case 0 valid addresses */ + /* TODO: Connect in parallel instead of serial, but that's a lot of work */ for (i = 0; i < numValidAddrs; i++) { res = Socket_Connect(&conn->socket, &addrs[i], false); @@ -636,9 +637,8 @@ static cc_result ConnectionPool_Open(struct HttpConnection** conn, const struct *#########################################################################################################################*/ enum HTTP_RESPONSE_STATE { HTTP_RESPONSE_STATE_HEADER, - HTTP_RESPONSE_STATE_BODY_DATA, + HTTP_RESPONSE_STATE_DATA, HTTP_RESPONSE_STATE_CHUNK_HEADER, - HTTP_RESPONSE_STATE_CHUNK_DATA, HTTP_RESPONSE_STATE_CHUNK_END_R, HTTP_RESPONSE_STATE_CHUNK_END_N, HTTP_RESPONSE_STATE_CHUNK_TRAILERS, @@ -649,8 +649,8 @@ struct HttpClientState { enum HTTP_RESPONSE_STATE state; struct HttpConnection* conn; struct HttpRequest* req; + cc_uint32 dataLeft; /* Number of bytes still to read from the current chunk or body */ int chunked; - int chunkRead, chunkLength; cc_bool autoClose; cc_string header, location; struct HttpUrl url; @@ -660,8 +660,7 @@ struct HttpClientState { static void HttpClientState_Reset(struct HttpClientState* state) { state->state = HTTP_RESPONSE_STATE_HEADER; state->chunked = 0; - state->chunkRead = 0; - state->chunkLength = 0; + state->dataLeft = 0; state->autoClose = false; String_InitArray(state->header, state->_headerBuffer); String_InitArray(state->location, state->_locationBuffer); @@ -752,7 +751,7 @@ static int HttpClient_BeginBody(struct HttpRequest* req, struct HttpClientState* } if (req->contentLength) { Http_BufferInit(req); - return HTTP_RESPONSE_STATE_BODY_DATA; + return HTTP_RESPONSE_STATE_DATA; } /* Zero length response */ return HTTP_RESPONSE_STATE_DONE; @@ -777,7 +776,8 @@ static int HttpClient_GetChunkLength(const cc_string* line) { /* https://httpwg.org/specs/rfc7230.html */ static cc_result HttpClient_Process(struct HttpClientState* state, char* buffer, int total) { struct HttpRequest* req = state->req; - int offset = 0; + cc_uint32 left, avail, read; + int offset = 0, chunkLen; while (offset < total) { switch (state->state) { @@ -792,6 +792,12 @@ static cc_result HttpClient_Process(struct HttpClientState* state, char* buffer, /* Zero length header = end of message headers */ if (state->header.length == 0) { state->state = HttpClient_BeginBody(req, state); + + /* The rest of the request body is just content/data */ + if (state->state == HTTP_RESPONSE_STATE_DATA) { + Http_BufferEnsure(req, req->contentLength); + state->dataLeft = req->contentLength; + } break; } @@ -802,23 +808,23 @@ static cc_result HttpClient_Process(struct HttpClientState* state, char* buffer, } break; - case HTTP_RESPONSE_STATE_BODY_DATA: + case HTTP_RESPONSE_STATE_DATA: { - cc_uint32 left = total - offset; - cc_uint32 avail = req->contentLength - req->size; - cc_uint32 read = min(left, avail); + left = total - offset; + avail = state->dataLeft; + read = min(left, avail); - Http_BufferEnsure(req, read); Mem_Copy(req->data + req->size, buffer + offset, read); - Http_BufferExpanded(req, read); + Http_BufferExpanded(req, read); state->dataLeft -= read; offset += read; - if (req->size >= req->contentLength) { - state->state = HTTP_RESPONSE_STATE_DONE; + if (!state->dataLeft) { + state->state = state->chunked ? HTTP_RESPONSE_STATE_CHUNK_END_R : HTTP_RESPONSE_STATE_DONE; } } break; + /* RFC 7230, section 4.1 - Chunked Transfer Coding */ case HTTP_RESPONSE_STATE_CHUNK_HEADER: { @@ -827,40 +833,22 @@ static cc_result HttpClient_Process(struct HttpClientState* state, char* buffer, if (c == '\r') continue; if (c != '\n') { String_Append(&state->header, c); continue; } - state->chunkLength = HttpClient_GetChunkLength(&state->header); - if (state->chunkLength < 0) return HTTP_ERR_CHUNK_SIZE; + chunkLen = HttpClient_GetChunkLength(&state->header); + if (chunkLen < 0) return HTTP_ERR_CHUNK_SIZE; state->header.length = 0; - if (state->chunkLength == 0) { + if (chunkLen == 0) { state->state = HTTP_RESPONSE_STATE_CHUNK_TRAILERS; } else { - state->state = HTTP_RESPONSE_STATE_CHUNK_DATA; + state->state = HTTP_RESPONSE_STATE_DATA; + Http_BufferEnsure(req, chunkLen); + state->dataLeft = chunkLen; } break; } } break; - case HTTP_RESPONSE_STATE_CHUNK_DATA: - { - cc_uint32 left = total - offset; - cc_uint32 avail = state->chunkLength - state->chunkRead; - cc_uint32 read = min(left, avail); - - Http_BufferEnsure(req, read); - Mem_Copy(req->data + req->size, buffer + offset, read); - Http_BufferExpanded(req, read); - state->chunkRead += read; - offset += read; - - if (state->chunkRead >= state->chunkLength) { - state->state = HTTP_RESPONSE_STATE_CHUNK_END_R; - state->chunkRead = 0; - state->chunkLength = 0; - } - } - break; - /* Chunks are terminated by \r\n */ case HTTP_RESPONSE_STATE_CHUNK_END_R: if (buffer[offset++] != '\r') return ERR_INVALID_ARGUMENT; @@ -899,17 +887,30 @@ static cc_result HttpClient_Process(struct HttpClientState* state, char* buffer, return 0; } +#define INPUT_BUFFER_LEN 8192 static cc_result HttpClient_ParseResponse(struct HttpClientState* state) { - char buffer[8192]; + struct HttpRequest* req = state->req; + char buffer[INPUT_BUFFER_LEN]; + char* dst; cc_uint32 total; cc_result res; for (;;) { - res = HttpConnection_Read(state->conn, buffer, 8192, &total); + dst = state->dataLeft > INPUT_BUFFER_LEN ? (req->data + req->size) : buffer; + res = HttpConnection_Read(state->conn, dst, INPUT_BUFFER_LEN, &total); + if (res) return res; if (total == 0) return ERR_END_OF_STREAM; - res = HttpClient_Process(state, buffer, total); + if (dst != buffer) { + /* When there is more than INPUT_BUFFER_LEN bytes of unread data/content, */ + /* there is no need to run the HTTP client state machine - just read directly */ + /* into the output buffer to avoid a pointless Mem_Copy call */ + Http_BufferExpanded(req, total); state->dataLeft -= total; + } else { + res = HttpClient_Process(state, buffer, total); + } + if (res) return res; if (state->state == HTTP_RESPONSE_STATE_DONE) return 0; } diff --git a/src/Platform.h b/src/Platform.h index 087071ef8..a8cfa56fb 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -243,7 +243,7 @@ CC_API void Waitable_WaitFor(void* handle, cc_uint32 milliseconds); void Platform_LoadSysFonts(void); #define CC_SOCKETADDR_MAXSIZE 512 -#define SOCKET_MAX_ADDRS 8 +#define SOCKET_MAX_ADDRS 5 typedef struct cc_sockaddr_ { int size; /* Actual size of the raw socket address */