Implement chunked transfer encoding for custom http client, refactor custom http client part 1

This commit is contained in:
UnknownShadow200 2023-05-23 22:30:49 +10:00
parent 0e94decae4
commit d53ef33e0e
2 changed files with 203 additions and 40 deletions

View File

@ -30,6 +30,7 @@ typedef cc_uint32 BitmapCol;
#define BITMAPCOLOR_B_MASK (0xFFU << BITMAPCOLOR_B_SHIFT) #define BITMAPCOLOR_B_MASK (0xFFU << BITMAPCOLOR_B_SHIFT)
#define BITMAPCOLOR_A_MASK (0xFFU << BITMAPCOLOR_A_SHIFT) #define BITMAPCOLOR_A_MASK (0xFFU << BITMAPCOLOR_A_SHIFT)
/* Extracts just the R/G/B/A component from a bitmap color */
#define BitmapCol_R(color) ((cc_uint8)(color >> BITMAPCOLOR_R_SHIFT)) #define BitmapCol_R(color) ((cc_uint8)(color >> BITMAPCOLOR_R_SHIFT))
#define BitmapCol_G(color) ((cc_uint8)(color >> BITMAPCOLOR_G_SHIFT)) #define BitmapCol_G(color) ((cc_uint8)(color >> BITMAPCOLOR_G_SHIFT))
#define BitmapCol_B(color) ((cc_uint8)(color >> BITMAPCOLOR_B_SHIFT)) #define BitmapCol_B(color) ((cc_uint8)(color >> BITMAPCOLOR_B_SHIFT))

View File

@ -416,8 +416,9 @@ static cc_result HttpBackend_Do(struct HttpRequest* req, cc_string* url) {
_curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, NULL); _curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, NULL);
return res; return res;
} }
#elif CC_BUILD_HTTPRAW #elif defined CC_BUILD_HTTPCLIENT
#include "Errors.h" #include "Errors.h"
#include "PackedCol.h"
static void HttpBackend_Init(void) { static void HttpBackend_Init(void) {
httpOnly = true; // TODO: insecure httpOnly = true; // TODO: insecure
@ -426,62 +427,119 @@ static void HttpBackend_Init(void) {
enum HTTP_RESPONSE_STATE { enum HTTP_RESPONSE_STATE {
HTTP_RESPONSE_STATE_HEADER, HTTP_RESPONSE_STATE_HEADER,
HTTP_RESPONSE_STATE_BODY_INIT, HTTP_RESPONSE_STATE_BODY_INIT,
HTTP_RESPONSE_STATE_BODY_DATA HTTP_RESPONSE_STATE_BODY_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,
HTTP_RESPONSE_STATE_DONE
}; };
/* https://httpwg.org/specs/rfc7230.html */ struct HttpClientState {
static cc_result HttpResponse_Parse(cc_socket socket, struct HttpRequest* req) { enum HTTP_RESPONSE_STATE state;
int state = HTTP_RESPONSE_STATE_HEADER; struct HttpRequest* req;
char headerBuffer[256]; int redirectTimes, chunked;
char buffer[4096]; int chunkRead, chunkLength;
cc_string header; cc_string header;
cc_uint32 total; char _headerBuffer[256];
cc_result res; };
int offset;
String_InitArray(header, headerBuffer);
/* TODO refactor */ static void HttpClientState_Reset(struct HttpClientState* state) {
for (;;) { state->state = HTTP_RESPONSE_STATE_HEADER;
res = Socket_Read(socket, buffer, 4096, &total); state->chunked = 0;
offset = 0; state->chunkRead = 0;
if (res) return res; state->chunkLength = 0;
if (total == 0) return ERR_END_OF_STREAM; String_InitArray(state->header, state->_headerBuffer);
}
switch (state) { static void HttpClientState_Init(struct HttpClientState* state) {
state->redirectTimes = 0;
HttpClientState_Reset(state);
}
static void HttpClient_ParseHeader(struct HttpClientState* state, const cc_string* line) {
cc_string name, value;
/* name: value */
if (!String_UNSAFE_Separate(line, ':', &name, &value)) return;
if (String_CaselessEqualsConst(&name, "Transfer-Encoding")) {
state->chunked = String_CaselessEqualsConst(&value, "chunked");
}
}
/* RFC 7230, section 3.3.3 - Message Body Length */
static cc_bool HttpClient_HasBody(struct HttpRequest* req) {
/* HEAD responses never have a message body */
if (req->requestType == REQUEST_TYPE_HEAD) return false;
/* 1XX (Information) responses don't have message body */
if (req->statusCode >= 100 && req->statusCode <= 199) return false;
/* 204 (No Content) and 304 (Not Modified) also don't */
if (req->statusCode == 204 || req->statusCode == 304) return false;
return true;
}
/* RFC 7230, section 4.1 - Chunked Transfer Coding */
static int HttpClient_GetChunkLength(const cc_string* line) {
int length = 0, i, part;
for (i = 0; i < line->length; i++) {
char c = line->buffer[i];
/* RFC 7230, section 4.1.1 - Chunk Extensions */
if (c == ';') break;
part = PackedCol_DeHex(c);
if (part == -1) return -1;
length = (length << 4) | part;
}
return length;
}
/* 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;
while (offset < total) {
switch (state->state) {
case HTTP_RESPONSE_STATE_HEADER: case HTTP_RESPONSE_STATE_HEADER:
{ {
for (; offset < total;) { for (; offset < total;) {
char c = buffer[offset++]; char c = buffer[offset++];
if (c == '\r') continue; if (c == '\r') continue;
if (c != '\n') { String_Append(&header, c); continue; } if (c != '\n') { String_Append(&state->header, c); continue; }
Http_ParseHeader(req, &header); /* Zero length header = end of message headers */
/* Zero length header = end of message header */ if (state->header.length == 0) {
if (header.length == 0) { state->state = HTTP_RESPONSE_STATE_BODY_INIT;
state = HTTP_RESPONSE_STATE_BODY_INIT; break;
goto handle_body_init;
} }
header.length = 0;
Http_ParseHeader(state->req, &state->header);
HttpClient_ParseHeader(state, &state->header);
state->header.length = 0;
} }
} }
break; break;
case HTTP_RESPONSE_STATE_BODY_INIT: case HTTP_RESPONSE_STATE_BODY_INIT:
handle_body_init:
{ {
/* Chunked encoding not supported yet */ if (!HttpClient_HasBody(req)) {
if (!req->contentLength) return res; state->state = HTTP_RESPONSE_STATE_DONE;
/* HEAD responses never have a message body */ } else if (state->chunked) {
if (req->requestType == REQUEST_TYPE_HEAD) return res; Http_BufferInit(req);
/* 1XX (Information) responses don't have message body */ state->state = HTTP_RESPONSE_STATE_CHUNK_HEADER;
if (req->statusCode >= 100 && req->statusCode <= 199) return res; } else if (req->contentLength) {
/* 204 (No Content) and 304 (Not Modified) also don't */ Http_BufferInit(req);
if (req->statusCode == 204 || req->statusCode == 304) return res; state->state = HTTP_RESPONSE_STATE_BODY_DATA;
} else {
Http_BufferInit(req); /* Chunked encoding not supported yet */
state = HTTP_RESPONSE_STATE_BODY_DATA; return ERR_NOT_SUPPORTED;
}
} }
break;
case HTTP_RESPONSE_STATE_BODY_DATA: case HTTP_RESPONSE_STATE_BODY_DATA:
{ {
@ -492,10 +550,110 @@ static cc_result HttpResponse_Parse(cc_socket socket, struct HttpRequest* req) {
Http_BufferEnsure(req, read); Http_BufferEnsure(req, read);
Mem_Copy(req->data + req->size, buffer + offset, read); Mem_Copy(req->data + req->size, buffer + offset, read);
Http_BufferExpanded(req, read); Http_BufferExpanded(req, read);
if (req->size >= req->contentLength) return res;
if (req->size >= req->contentLength) {
state->state = HTTP_RESPONSE_STATE_DONE;
}
} }
break; break;
/* RFC 7230, section 4.1 - Chunked Transfer Coding */
case HTTP_RESPONSE_STATE_CHUNK_HEADER:
{
for (; offset < total;) {
char c = buffer[offset++];
if (c == '\r') continue;
if (c != '\n') { String_Append(&state->header, c); continue; }
state->chunkLength = HttpClient_GetChunkLength(&state->header);
if (state->chunkLength < 0) return ERR_INVALID_ARGUMENT;
state->header.length = 0;
if (state->chunkLength == 0) {
state->state = HTTP_RESPONSE_STATE_CHUNK_TRAILERS;
} else {
state->state = HTTP_RESPONSE_STATE_CHUNK_DATA;
}
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;
state->state = HTTP_RESPONSE_STATE_CHUNK_END_N;
break;
case HTTP_RESPONSE_STATE_CHUNK_END_N:
if (buffer[offset++] != '\n') return ERR_INVALID_ARGUMENT;
state->state = HTTP_RESPONSE_STATE_CHUNK_HEADER;
break;
/* RFC 7230, section 4.1.2 - Chunked Trailer Part */
case HTTP_RESPONSE_STATE_CHUNK_TRAILERS:
{
for (; offset < total;) {
char c = buffer[offset++];
if (c == '\r') continue;
if (c != '\n') { String_Append(&state->header, c); continue; }
/* Zero length header = end of message trailers */
if (state->header.length == 0) {
state->state = HTTP_RESPONSE_STATE_DONE;
break;
}
state->header.length = 0;
}
}
break;
default:
return 0;
}
}
return 0;
}
static cc_result HttpClient_ParseResponse(cc_socket socket, struct HttpRequest* req) {
char buffer[8192];
cc_uint32 total;
cc_result res;
struct HttpClientState state;
HttpClientState_Init(&state);
state.req = req;
for (;;) {
res = Socket_Read(socket, buffer, 8192, &total);
if (res) return res;
if (total == 0) return ERR_END_OF_STREAM;
res = HttpClient_Process(&state, buffer, total);
if (res) return res;
if (state.state == HTTP_RESPONSE_STATE_DONE) return 0;
} }
} }
@ -561,6 +719,7 @@ static void Http_AddHeader(struct HttpRequest* req, const char* key, const cc_st
} }
static cc_result HttpBackend_Do(struct HttpRequest* req, cc_string* url) { static cc_result HttpBackend_Do(struct HttpRequest* req, cc_string* url) {
static const cc_string userAgent = String_FromConst(GAME_APP_NAME);
static const char* verbs[3] = { "GET", "HEAD", "POST" }; static const char* verbs[3] = { "GET", "HEAD", "POST" };
struct HttpUrlParts parts; struct HttpUrlParts parts;
char inputBuffer[16384]; char inputBuffer[16384];
@ -574,11 +733,13 @@ static cc_result HttpBackend_Do(struct HttpRequest* req, cc_string* url) {
String_InitArray(inputMsg, inputBuffer); String_InitArray(inputMsg, inputBuffer);
req->meta = &inputMsg; req->meta = &inputMsg;
http_curProgress = HTTP_PROGRESS_FETCHING_DATA;
/* Write request message headers */ /* Write request message headers */
String_Format2(&inputMsg, "%c %s HTTP/1.1\r\n", String_Format2(&inputMsg, "%c %s HTTP/1.1\r\n",
verbs[req->requestType], &parts.resource); verbs[req->requestType], &parts.resource);
Http_AddHeader(req, "Host", &parts.address); Http_AddHeader(req, "Host", &parts.address);
Http_AddHeader(req, "User-Agent", &userAgent);
Http_SetRequestHeaders(req); Http_SetRequestHeaders(req);
String_AppendConst(&inputMsg, "\r\n"); String_AppendConst(&inputMsg, "\r\n");
@ -592,7 +753,8 @@ static cc_result HttpBackend_Do(struct HttpRequest* req, cc_string* url) {
res = Socket_Write(socket, inputBuffer, inputMsg.length, &wrote); res = Socket_Write(socket, inputBuffer, inputMsg.length, &wrote);
if (res) { Socket_Close(socket); return res; } if (res) { Socket_Close(socket); return res; }
res = HttpResponse_Parse(socket, req); res = HttpClient_ParseResponse(socket, req);
http_curProgress = 100;
Socket_Close(socket); Socket_Close(socket);
return res; return res;
} }