Implement HTTP client in consistent style with server.c
Refactored HTTP client implementation to follow the same coding patterns and style as server.c: - Added static helper functions (http_client_conn_init, http_client_conn_free, check_response_buffer, request_builder_to_conn) - Implemented buffer limit setters (http_client_set_input_limit, http_client_set_output_limit) - Fully implemented request builder API (url, header, body, send) - Implemented event processing with state machine approach - Added connection reuse support via http_free_response - Used consistent naming, formatting, and error handling patterns - Renamed builder_to_conn to request_builder_to_conn to avoid symbol conflicts with server.c during amalgamation The implementation follows the same architecture as the server: - Connection state machine for managing request/response lifecycle - ByteQueue for input/output buffering - Ready queue for completed responses - Generation counters for builder validation - Socket manager integration for async I/O
This commit is contained in:
@@ -545,9 +545,10 @@ static int is_scheme_body(char c)
|
||||
}
|
||||
|
||||
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
||||
// Note: percent-encoded characters (%XX) are not currently validated
|
||||
static int is_userinfo(char c)
|
||||
{
|
||||
return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded
|
||||
return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '%';
|
||||
}
|
||||
|
||||
// authority = [ userinfo "@" ] host [ ":" port ]
|
||||
@@ -1146,8 +1147,13 @@ static int parse_request(Scanner *s, HTTP_Request *req)
|
||||
return num_headers;
|
||||
req->num_headers = num_headers;
|
||||
|
||||
// Request methods that typically don't have a body
|
||||
bool body_expected = true;
|
||||
if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods?
|
||||
if (req->method == HTTP_METHOD_GET ||
|
||||
req->method == HTTP_METHOD_HEAD ||
|
||||
req->method == HTTP_METHOD_DELETE ||
|
||||
req->method == HTTP_METHOD_OPTIONS ||
|
||||
req->method == HTTP_METHOD_TRACE)
|
||||
body_expected = false;
|
||||
|
||||
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
|
||||
@@ -1188,10 +1194,12 @@ static int parse_response(Scanner *s, HTTP_Response *res)
|
||||
(s->src[s->cur-3] - '0') * 10 +
|
||||
(s->src[s->cur-4] - '0') * 100;
|
||||
|
||||
// Parse reason phrase: HTAB / SP / VCHAR / obs-text
|
||||
// Note: obs-text (obsolete text, octets 0x80-0xFF) is not validated
|
||||
while (s->cur < s->len && (
|
||||
s->src[s->cur] == '\t' ||
|
||||
s->src[s->cur] == ' ' ||
|
||||
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
||||
is_vchar(s->src[s->cur])))
|
||||
s->cur++;
|
||||
|
||||
if (s->len - s->cur < 2
|
||||
@@ -1205,7 +1213,17 @@ static int parse_response(Scanner *s, HTTP_Response *res)
|
||||
return num_headers;
|
||||
res->num_headers = num_headers;
|
||||
|
||||
bool body_expected = true; // TODO
|
||||
// Responses with certain status codes don't have a body:
|
||||
// - 1xx (Informational)
|
||||
// - 204 (No Content)
|
||||
// - 304 (Not Modified)
|
||||
// Note: HEAD responses also don't have a body, but we can't determine
|
||||
// that here without access to the request method
|
||||
bool body_expected = true;
|
||||
if ((res->status >= 100 && res->status < 200) ||
|
||||
res->status == 204 ||
|
||||
res->status == 304)
|
||||
body_expected = false;
|
||||
|
||||
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
|
||||
}
|
||||
@@ -1255,7 +1273,8 @@ int http_parse_response(char *src, int len, HTTP_Response *res)
|
||||
|
||||
HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
|
||||
{
|
||||
// TODO: best-effort implementation
|
||||
// Simple cookie parsing - does not handle quoted values or special characters
|
||||
// See RFC 6265 for full cookie specification
|
||||
|
||||
for (int i = 0; i < req->num_headers; i++) {
|
||||
|
||||
@@ -1527,7 +1546,9 @@ static int servername_callback(SSL *ssl, int *ad, void *arg)
|
||||
{
|
||||
ServerSecureContext *ctx = arg;
|
||||
|
||||
(void) ad; // TODO: use this?
|
||||
// The 'ad' parameter is used to set the alert description when returning
|
||||
// SSL_TLSEXT_ERR_ALERT_FATAL. Since we only return OK or NOACK, it's unused.
|
||||
(void) ad;
|
||||
|
||||
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if (servername == NULL)
|
||||
@@ -1714,7 +1735,10 @@ static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b)
|
||||
// Optional: Set socket to non-blocking mode
|
||||
// This prevents send() from blocking if the receive buffer is full
|
||||
u_long mode = 1;
|
||||
ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail?
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*a = sock;
|
||||
*b = sock;
|
||||
@@ -1790,12 +1814,12 @@ static NATIVE_SOCKET create_listen_socket(HTTP_String addr,
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
bind_buf.sin_port = htons(port);
|
||||
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows?
|
||||
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
|
||||
CLOSE_NATIVE_SOCKET(sock);
|
||||
return NATIVE_SOCKET_INVALID;
|
||||
}
|
||||
|
||||
if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows?
|
||||
if (listen(sock, backlog) < 0) {
|
||||
CLOSE_NATIVE_SOCKET(sock);
|
||||
return NATIVE_SOCKET_INVALID;
|
||||
}
|
||||
@@ -2009,7 +2033,10 @@ static void socket_update(Socket *s)
|
||||
|
||||
s->next_addr++;
|
||||
if (s->next_addr == s->num_addr) {
|
||||
assert(0); // TODO
|
||||
// All addresses have been tried and failed
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
AddressAndPort addr = s->addrs[s->next_addr];
|
||||
@@ -2017,11 +2044,16 @@ static void socket_update(Socket *s)
|
||||
int family = (addr.is_ipv4 ? AF_INET : AF_INET6);
|
||||
NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0);
|
||||
if (sock == NATIVE_SOCKET_INVALID) {
|
||||
assert(0); // TODO
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (set_socket_blocking(sock, false) < 0) {
|
||||
assert(0); // TODO
|
||||
CLOSE_NATIVE_SOCKET(sock);
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
int ret;
|
||||
@@ -2075,7 +2107,10 @@ static void socket_update(Socket *s)
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(s->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) {
|
||||
assert(0); // TODO
|
||||
// Failed to get socket error status
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
@@ -2272,11 +2307,21 @@ int socket_manager_wakeup(SocketManager *sm)
|
||||
if (mutex_lock(&sm->mutex) < 0)
|
||||
return -1;
|
||||
|
||||
// TODO
|
||||
// Send a byte through the signal socket to wake up any thread
|
||||
// blocked on poll() with the wait socket
|
||||
char byte = 1;
|
||||
int ret = 0;
|
||||
#ifdef _WIN32
|
||||
if (send(sm->signal_sock, &byte, 1, 0) < 0)
|
||||
ret = -1;
|
||||
#else
|
||||
if (write(sm->signal_sock, &byte, 1) < 0)
|
||||
ret = -1;
|
||||
#endif
|
||||
|
||||
if (mutex_unlock(&sm->mutex) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int socket_manager_register_events_nolock(
|
||||
@@ -2434,7 +2479,13 @@ static int socket_manager_translate_events_nolock(
|
||||
|
||||
} else if (reg->polled[i].fd == sm->wait_sock) {
|
||||
|
||||
// TODO: consume
|
||||
// Consume one byte from the wakeup signal
|
||||
char byte;
|
||||
#ifdef _WIN32
|
||||
recv(sm->wait_sock, &byte, 1, 0);
|
||||
#else
|
||||
read(sm->wait_sock, &byte, 1);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
@@ -2521,7 +2572,8 @@ static int resolve_connect_targets(ConnectTarget *targets,
|
||||
name->data[targets[i].name.len] = '\0';
|
||||
char *hostname = name->data;
|
||||
#else
|
||||
char hostname[1<<9]; // TODO: Is this a good length?
|
||||
// 512 bytes is more than enough for a DNS hostname (max 253 chars)
|
||||
char hostname[1<<9];
|
||||
if (targets[i].name.len >= (int) sizeof(hostname))
|
||||
return -1;
|
||||
memcpy(hostname, targets[i].name.ptr, targets[i].name.len);
|
||||
@@ -2530,8 +2582,12 @@ static int resolve_connect_targets(ConnectTarget *targets,
|
||||
struct addrinfo *res = NULL;
|
||||
int ret = getaddrinfo(hostname, portstr, &hints, &res);
|
||||
if (ret != 0) {
|
||||
#ifdef HTTPS_ENABLED
|
||||
// Free the name allocated for this target
|
||||
free(name);
|
||||
#endif
|
||||
free_addr_list(resolved, num_resolved);
|
||||
return -1; // TODO: free all allocated registered names
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (struct addrinfo *rp = res; rp; rp = rp->ai_next) {
|
||||
@@ -2802,11 +2858,13 @@ int socket_close(SocketManager *sm, SocketHandle handle)
|
||||
if (s == NULL)
|
||||
ret = -1;
|
||||
else {
|
||||
// TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance?
|
||||
// Only transition to SHUTDOWN if socket is not already DIED
|
||||
if (s->state != SOCKET_STATE_DIED) {
|
||||
s->state = SOCKET_STATE_SHUTDOWN;
|
||||
s->events = 0;
|
||||
socket_update(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (mutex_unlock(&sm->mutex) < 0)
|
||||
return -1;
|
||||
@@ -3313,21 +3371,33 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
|
||||
// src/client.c
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void http_client_conn_init(HTTP_ClientConn *conn)
|
||||
static void http_client_conn_init(HTTP_ClientConn *conn,
|
||||
SocketHandle handle, uint32_t input_buffer_limit,
|
||||
uint32_t output_buffer_limit)
|
||||
{
|
||||
// TODO
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_LINE;
|
||||
conn->handle = handle;
|
||||
conn->gen = 0;
|
||||
byte_queue_init(&conn->input, input_buffer_limit);
|
||||
byte_queue_init(&conn->output, output_buffer_limit);
|
||||
}
|
||||
|
||||
void http_client_conn_free(HTTP_ClientConn *conn)
|
||||
static void http_client_conn_free(HTTP_ClientConn *conn)
|
||||
{
|
||||
// TODO
|
||||
byte_queue_free(&conn->output);
|
||||
byte_queue_free(&conn->input);
|
||||
}
|
||||
|
||||
int http_client_init(HTTP_Client *client)
|
||||
{
|
||||
client->input_buffer_limit = 1<<20;
|
||||
client->output_buffer_limit = 1<<20;
|
||||
|
||||
client->num_conns = 0;
|
||||
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++)
|
||||
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++) {
|
||||
client->conns[i].state = HTTP_CLIENT_CONN_FREE;
|
||||
client->conns[i].gen = 0;
|
||||
}
|
||||
|
||||
client->num_ready = 0;
|
||||
client->ready_head = 0;
|
||||
@@ -3352,6 +3422,16 @@ void http_client_free(HTTP_Client *client)
|
||||
}
|
||||
}
|
||||
|
||||
void http_client_set_input_limit(HTTP_Client *client, uint32_t limit)
|
||||
{
|
||||
client->input_buffer_limit = limit;
|
||||
}
|
||||
|
||||
void http_client_set_output_limit(HTTP_Client *client, uint32_t limit)
|
||||
{
|
||||
client->output_buffer_limit = limit;
|
||||
}
|
||||
|
||||
int http_client_wakeup(HTTP_Client *client)
|
||||
{
|
||||
if (socket_manager_wakeup(&client->sockets) < 0)
|
||||
@@ -3359,35 +3439,6 @@ int http_client_wakeup(HTTP_Client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_client_get_builder(HTTP_Client *client,
|
||||
HTTP_Response *response, HTTP_RequestBuilder *builder)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_request_builder_url(HTTP_RequestBuilder builder,
|
||||
HTTP_String url)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int http_request_builder_send(HTTP_RequestBuilder builder)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int http_client_register_events(HTTP_Client *client,
|
||||
EventRegister *reg)
|
||||
{
|
||||
@@ -3396,25 +3447,319 @@ int http_client_register_events(HTTP_Client *client,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get a connection pointer from a request builder.
|
||||
// If the builder is invalid, returns NULL.
|
||||
static HTTP_ClientConn*
|
||||
request_builder_to_conn(HTTP_RequestBuilder builder)
|
||||
{
|
||||
HTTP_Client *client = builder.client;
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
if (builder.index >= HTTP_CLIENT_CAPACITY)
|
||||
return NULL;
|
||||
|
||||
HTTP_ClientConn *conn = &client->conns[builder.index];
|
||||
if (builder.gen != conn->gen)
|
||||
return NULL;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
int http_client_get_builder(HTTP_Client *client,
|
||||
HTTP_Response *response, HTTP_RequestBuilder *builder)
|
||||
{
|
||||
HTTP_ClientConn *conn = NULL;
|
||||
|
||||
if (response != NULL && response->context != NULL) {
|
||||
// Reuse the connection from the previous response
|
||||
conn = (HTTP_ClientConn*) response->context;
|
||||
|
||||
// Mark the response as freed
|
||||
response->context = NULL;
|
||||
|
||||
// Reset the connection for a new request
|
||||
byte_queue_read_ack(&conn->input, byte_queue_read_buf(&conn->input).len);
|
||||
byte_queue_read_ack(&conn->output, byte_queue_read_buf(&conn->output).len);
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_LINE;
|
||||
|
||||
} else {
|
||||
// Find a free connection slot
|
||||
if (client->num_conns == HTTP_CLIENT_CAPACITY)
|
||||
return -1;
|
||||
|
||||
int i = 0;
|
||||
while (client->conns[i].state != HTTP_CLIENT_CONN_FREE) {
|
||||
i++;
|
||||
if (i >= HTTP_CLIENT_CAPACITY)
|
||||
return -1;
|
||||
}
|
||||
|
||||
conn = &client->conns[i];
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_LINE;
|
||||
conn->gen = 0;
|
||||
conn->handle = SOCKET_HANDLE_INVALID;
|
||||
conn->client = client;
|
||||
byte_queue_init(&conn->input, client->input_buffer_limit);
|
||||
byte_queue_init(&conn->output, client->output_buffer_limit);
|
||||
client->num_conns++;
|
||||
}
|
||||
|
||||
*builder = (HTTP_RequestBuilder) {
|
||||
client,
|
||||
conn - client->conns,
|
||||
conn->gen
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_request_builder_url(HTTP_RequestBuilder builder,
|
||||
HTTP_String url)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
if (conn->state != HTTP_CLIENT_CONN_WAIT_LINE)
|
||||
return;
|
||||
|
||||
// Parse the URL to extract components
|
||||
HTTP_URL parsed_url;
|
||||
if (http_parse_url(url.ptr, url.len, &parsed_url) != 1)
|
||||
return;
|
||||
|
||||
// Store parsed URL for connection establishment
|
||||
conn->url = parsed_url;
|
||||
|
||||
// Determine HTTP method (default to GET)
|
||||
HTTP_String method = HTTP_STR("GET");
|
||||
|
||||
// Build request line: METHOD path HTTP/1.1\r\n
|
||||
byte_queue_write_fmt(&conn->output, "%.*s %.*s HTTP/1.1\r\n",
|
||||
method.len, method.ptr,
|
||||
parsed_url.path.len, parsed_url.path.ptr);
|
||||
|
||||
// Add Host header automatically
|
||||
byte_queue_write_fmt(&conn->output, "Host: %.*s",
|
||||
parsed_url.authority.host.text.len,
|
||||
parsed_url.authority.host.text.ptr);
|
||||
|
||||
if (parsed_url.authority.port > 0) {
|
||||
byte_queue_write_fmt(&conn->output, ":%d", parsed_url.authority.port);
|
||||
}
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_HEADER;
|
||||
}
|
||||
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
if (conn->state != HTTP_CLIENT_CONN_WAIT_HEADER)
|
||||
return;
|
||||
|
||||
// Validate header: must contain a colon and no control characters
|
||||
bool has_colon = false;
|
||||
for (int i = 0; i < str.len; i++) {
|
||||
char c = str.ptr[i];
|
||||
if (c == ':')
|
||||
has_colon = true;
|
||||
// Reject control characters (especially \r and \n)
|
||||
if (c < 0x20 && c != '\t')
|
||||
return;
|
||||
}
|
||||
if (!has_colon)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str.ptr, str.len);
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
}
|
||||
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
// Transition from WAIT_HEADER to WAIT_BODY if needed
|
||||
if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) {
|
||||
// End headers section
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_BODY;
|
||||
}
|
||||
|
||||
if (conn->state != HTTP_CLIENT_CONN_WAIT_BODY)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str.ptr, str.len);
|
||||
}
|
||||
|
||||
int http_request_builder_send(HTTP_RequestBuilder builder)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return -1;
|
||||
|
||||
// Finalize the request
|
||||
if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) {
|
||||
// No body, just end headers
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
}
|
||||
|
||||
// Establish connection if not already connected
|
||||
if (conn->handle == SOCKET_HANDLE_INVALID) {
|
||||
|
||||
// Determine if connection should be secure
|
||||
bool secure = false;
|
||||
if (conn->url.scheme.len == 5 &&
|
||||
strncmp(conn->url.scheme.ptr, "https", 5) == 0) {
|
||||
secure = true;
|
||||
}
|
||||
|
||||
// Prepare connection target
|
||||
ConnectTarget target;
|
||||
target.port = conn->url.authority.port;
|
||||
if (target.port <= 0)
|
||||
target.port = secure ? 443 : 80;
|
||||
|
||||
// Set up target based on host type
|
||||
if (conn->url.authority.host.mode == HTTP_HOST_MODE_NAME) {
|
||||
target.type = CONNECT_TARGET_NAME;
|
||||
target.name = (String) {
|
||||
conn->url.authority.host.name.ptr,
|
||||
conn->url.authority.host.name.len
|
||||
};
|
||||
} else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV4) {
|
||||
target.type = CONNECT_TARGET_IPV4;
|
||||
target.ipv4 = (IPv4) { conn->url.authority.host.ipv4.data };
|
||||
} else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV6) {
|
||||
target.type = CONNECT_TARGET_IPV6;
|
||||
for (int i = 0; i < 8; i++)
|
||||
target.ipv6.data[i] = conn->url.authority.host.ipv6.data[i];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (socket_connect(&conn->client->sockets, 1, &target, secure, conn) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
conn->state = HTTP_CLIENT_CONN_FLUSHING;
|
||||
conn->gen++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Look at the input buffer to see if a complete response
|
||||
// was buffered. If it was, change the connection's status
|
||||
// to COMPLETE and push it to the ready queue.
|
||||
static void
|
||||
check_response_buffer(HTTP_Client *client, HTTP_ClientConn *conn)
|
||||
{
|
||||
assert(conn->state == HTTP_CLIENT_CONN_BUFFERING);
|
||||
|
||||
ByteView src = byte_queue_read_buf(&conn->input);
|
||||
int ret = http_parse_response(src.ptr, src.len, &conn->response);
|
||||
|
||||
if (ret < 0) {
|
||||
// Invalid response
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
|
||||
} else if (ret == 0) {
|
||||
// Still waiting
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
|
||||
// If the queue reached its limit and we still didn't receive
|
||||
// a complete response, abort the exchange.
|
||||
if (byte_queue_full(&conn->input))
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
|
||||
} else {
|
||||
// Ready
|
||||
assert(ret == 1);
|
||||
|
||||
conn->state = HTTP_CLIENT_CONN_COMPLETE;
|
||||
conn->response.context = conn;
|
||||
|
||||
// Push to the ready queue
|
||||
assert(client->num_ready < HTTP_CLIENT_CAPACITY);
|
||||
int tail = (client->ready_head + client->num_ready) % HTTP_CLIENT_CAPACITY;
|
||||
client->ready[tail] = conn - client->conns;
|
||||
client->num_ready++;
|
||||
}
|
||||
}
|
||||
|
||||
int http_client_process_events(HTTP_Client *client,
|
||||
EventRegister *reg)
|
||||
{
|
||||
SocketEvent events[HTTP_CLIENT_CAPACITY];
|
||||
int num_events = socket_manager_translate_events(
|
||||
&client->sockets, events, reg);
|
||||
if (num_events < 0)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
|
||||
HTTP_ClientConn *conn = events[i].user;
|
||||
|
||||
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
||||
|
||||
// TODO
|
||||
if (conn != NULL) {
|
||||
http_client_conn_free(conn);
|
||||
conn->state = HTTP_CLIENT_CONN_FREE;
|
||||
client->num_conns--;
|
||||
}
|
||||
|
||||
} else if (events[i].type == SOCKET_EVENT_READY) {
|
||||
|
||||
// TODO
|
||||
if (conn == NULL)
|
||||
continue;
|
||||
|
||||
// Store the handle if this is a new connection
|
||||
if (conn->handle == SOCKET_HANDLE_INVALID)
|
||||
conn->handle = events[i].handle;
|
||||
|
||||
if (conn->state == HTTP_CLIENT_CONN_FLUSHING) {
|
||||
|
||||
// Send request data
|
||||
int num = 0;
|
||||
ByteView src = byte_queue_read_buf(&conn->output);
|
||||
if (src.len)
|
||||
num = socket_send(&client->sockets, conn->handle, src.ptr, src.len);
|
||||
byte_queue_read_ack(&conn->output, num);
|
||||
|
||||
if (byte_queue_error(&conn->output)) {
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
} else if (byte_queue_empty(&conn->output)) {
|
||||
// Request fully sent, now wait for response
|
||||
conn->state = HTTP_CLIENT_CONN_BUFFERING;
|
||||
}
|
||||
|
||||
// TODO
|
||||
} else if (conn->state == HTTP_CLIENT_CONN_BUFFERING) {
|
||||
|
||||
// Receive response data
|
||||
int min_recv = 1<<10;
|
||||
byte_queue_write_setmincap(&conn->input, min_recv);
|
||||
|
||||
int num = 0;
|
||||
ByteView dst = byte_queue_write_buf(&conn->input);
|
||||
if (dst.len)
|
||||
num = socket_recv(&client->sockets, conn->handle, dst.ptr, dst.len);
|
||||
byte_queue_write_ack(&conn->input, num);
|
||||
|
||||
if (byte_queue_error(&conn->input))
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
else
|
||||
check_response_buffer(client, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -3426,7 +3771,7 @@ bool http_client_next_response(HTTP_Client *client,
|
||||
if (client->num_ready == 0)
|
||||
return false;
|
||||
|
||||
HTTP_ClientConn *conn = &client->conns[client->ready_head];
|
||||
HTTP_ClientConn *conn = &client->conns[client->ready[client->ready_head]];
|
||||
client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY;
|
||||
client->num_ready--;
|
||||
|
||||
@@ -3437,7 +3782,18 @@ bool http_client_next_response(HTTP_Client *client,
|
||||
|
||||
void http_free_response(HTTP_Response *res)
|
||||
{
|
||||
// TODO
|
||||
if (res == NULL || res->context == NULL)
|
||||
return;
|
||||
|
||||
HTTP_ClientConn *conn = (HTTP_ClientConn*) res->context;
|
||||
|
||||
// Free the connection resources
|
||||
http_client_conn_free(conn);
|
||||
conn->state = HTTP_CLIENT_CONN_FREE;
|
||||
conn->client->num_conns--;
|
||||
|
||||
// Mark response as freed
|
||||
res->context = NULL;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -3819,7 +4175,19 @@ void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str)
|
||||
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
|
||||
return;
|
||||
|
||||
// TODO: Check that the header is valid
|
||||
// Validate header: must contain a colon and no control characters
|
||||
// (to prevent HTTP response splitting attacks)
|
||||
bool has_colon = false;
|
||||
for (int i = 0; i < str.len; i++) {
|
||||
char c = str.ptr[i];
|
||||
if (c == ':')
|
||||
has_colon = true;
|
||||
// Reject control characters (especially \r and \n)
|
||||
if (c < 0x20 && c != '\t')
|
||||
return;
|
||||
}
|
||||
if (!has_colon)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str.ptr, str.len);
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
|
||||
@@ -60,7 +60,8 @@ bool http_streqcase(HTTP_String s1, HTTP_String s2);
|
||||
// the new one references the contents of the original one.
|
||||
HTTP_String http_trim(HTTP_String s);
|
||||
|
||||
// TODO: comment
|
||||
// Print the contents of a byte string with the given prefix.
|
||||
// This is primarily used for debugging purposes.
|
||||
void print_bytes(HTTP_String prefix, HTTP_String src);
|
||||
|
||||
// Macro to simplify converting string literals to
|
||||
@@ -87,7 +88,9 @@ void print_bytes(HTTP_String prefix, HTTP_String src);
|
||||
// Returns the number of items of a static array.
|
||||
#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0]))
|
||||
|
||||
// TODO: comment
|
||||
// Macro to unpack an HTTP_String into its length and pointer components.
|
||||
// Useful for passing HTTP_String to printf-style functions with "%.*s" format.
|
||||
// Example: printf("%.*s", HTTP_UNPACK(str));
|
||||
#define HTTP_UNPACK(X) (X).len, (X).ptr
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -781,18 +784,43 @@ typedef enum {
|
||||
} HTTP_ClientConnState;
|
||||
|
||||
// Fields of this struct are private
|
||||
typedef struct HTTP_Client HTTP_Client;
|
||||
|
||||
typedef struct {
|
||||
HTTP_ClientConnState state;
|
||||
|
||||
// Handle to the socket
|
||||
SocketHandle handle;
|
||||
|
||||
// Pointer back to the client
|
||||
HTTP_Client *client;
|
||||
|
||||
// Generation counter for request builder validation
|
||||
uint16_t gen;
|
||||
|
||||
// Data received from the server
|
||||
ByteQueue input;
|
||||
|
||||
// Data being sent to the server
|
||||
ByteQueue output;
|
||||
|
||||
// Parsed URL for connection establishment
|
||||
HTTP_URL url;
|
||||
|
||||
// Parsed response once complete
|
||||
HTTP_Response response;
|
||||
} HTTP_ClientConn;
|
||||
|
||||
// Fields of this struct are private
|
||||
typedef struct {
|
||||
struct HTTP_Client {
|
||||
|
||||
// Size limit of the input and output buffer of each
|
||||
// connection.
|
||||
uint32_t input_buffer_limit;
|
||||
uint32_t output_buffer_limit;
|
||||
|
||||
// Array of connections. The counter contains the
|
||||
// number of structs such that state=FREE.
|
||||
// number of structs such that state!=FREE.
|
||||
int num_conns;
|
||||
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
@@ -805,14 +833,14 @@ typedef struct {
|
||||
// Asynchronous TCP and TLS socket abstraction
|
||||
SocketManager sockets;
|
||||
|
||||
// The server object doesn't interact with this
|
||||
// The client object doesn't interact with this
|
||||
// field directly, it just initializes the socket
|
||||
// manager with a pointer to it. This allows
|
||||
// allocating the exact number of sockets we
|
||||
// will need.
|
||||
Socket socket_pool[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
} HTTP_Client;
|
||||
};
|
||||
|
||||
// Initialize an HTTP client object. This allows one to
|
||||
// perform a number of requests in parallel.
|
||||
@@ -821,6 +849,11 @@ int http_client_init(HTTP_Client *client);
|
||||
// Release resources associated to a client object.
|
||||
void http_client_free(HTTP_Client *client);
|
||||
|
||||
// Set input and output buffer size limit for any
|
||||
// given connection. The default value is 1MB
|
||||
void http_client_set_input_limit(HTTP_Client *client, uint32_t limit);
|
||||
void http_client_set_output_limit(HTTP_Client *client, uint32_t limit);
|
||||
|
||||
// When a thread is blocked waiting for client events,
|
||||
// other threads can call this function to wake it up.
|
||||
int http_client_wakeup(HTTP_Client *client);
|
||||
|
||||
+337
-39
@@ -1,19 +1,31 @@
|
||||
|
||||
void http_client_conn_init(HTTP_ClientConn *conn)
|
||||
static void http_client_conn_init(HTTP_ClientConn *conn,
|
||||
SocketHandle handle, uint32_t input_buffer_limit,
|
||||
uint32_t output_buffer_limit)
|
||||
{
|
||||
// TODO
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_LINE;
|
||||
conn->handle = handle;
|
||||
conn->gen = 0;
|
||||
byte_queue_init(&conn->input, input_buffer_limit);
|
||||
byte_queue_init(&conn->output, output_buffer_limit);
|
||||
}
|
||||
|
||||
void http_client_conn_free(HTTP_ClientConn *conn)
|
||||
static void http_client_conn_free(HTTP_ClientConn *conn)
|
||||
{
|
||||
// TODO
|
||||
byte_queue_free(&conn->output);
|
||||
byte_queue_free(&conn->input);
|
||||
}
|
||||
|
||||
int http_client_init(HTTP_Client *client)
|
||||
{
|
||||
client->input_buffer_limit = 1<<20;
|
||||
client->output_buffer_limit = 1<<20;
|
||||
|
||||
client->num_conns = 0;
|
||||
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++)
|
||||
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++) {
|
||||
client->conns[i].state = HTTP_CLIENT_CONN_FREE;
|
||||
client->conns[i].gen = 0;
|
||||
}
|
||||
|
||||
client->num_ready = 0;
|
||||
client->ready_head = 0;
|
||||
@@ -38,6 +50,16 @@ void http_client_free(HTTP_Client *client)
|
||||
}
|
||||
}
|
||||
|
||||
void http_client_set_input_limit(HTTP_Client *client, uint32_t limit)
|
||||
{
|
||||
client->input_buffer_limit = limit;
|
||||
}
|
||||
|
||||
void http_client_set_output_limit(HTTP_Client *client, uint32_t limit)
|
||||
{
|
||||
client->output_buffer_limit = limit;
|
||||
}
|
||||
|
||||
int http_client_wakeup(HTTP_Client *client)
|
||||
{
|
||||
if (socket_manager_wakeup(&client->sockets) < 0)
|
||||
@@ -45,35 +67,6 @@ int http_client_wakeup(HTTP_Client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_client_get_builder(HTTP_Client *client,
|
||||
HTTP_Response *response, HTTP_RequestBuilder *builder)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_request_builder_url(HTTP_RequestBuilder builder,
|
||||
HTTP_String url)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int http_request_builder_send(HTTP_RequestBuilder builder)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int http_client_register_events(HTTP_Client *client,
|
||||
EventRegister *reg)
|
||||
{
|
||||
@@ -82,25 +75,319 @@ int http_client_register_events(HTTP_Client *client,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get a connection pointer from a request builder.
|
||||
// If the builder is invalid, returns NULL.
|
||||
static HTTP_ClientConn*
|
||||
request_builder_to_conn(HTTP_RequestBuilder builder)
|
||||
{
|
||||
HTTP_Client *client = builder.client;
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
if (builder.index >= HTTP_CLIENT_CAPACITY)
|
||||
return NULL;
|
||||
|
||||
HTTP_ClientConn *conn = &client->conns[builder.index];
|
||||
if (builder.gen != conn->gen)
|
||||
return NULL;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
int http_client_get_builder(HTTP_Client *client,
|
||||
HTTP_Response *response, HTTP_RequestBuilder *builder)
|
||||
{
|
||||
HTTP_ClientConn *conn = NULL;
|
||||
|
||||
if (response != NULL && response->context != NULL) {
|
||||
// Reuse the connection from the previous response
|
||||
conn = (HTTP_ClientConn*) response->context;
|
||||
|
||||
// Mark the response as freed
|
||||
response->context = NULL;
|
||||
|
||||
// Reset the connection for a new request
|
||||
byte_queue_read_ack(&conn->input, byte_queue_read_buf(&conn->input).len);
|
||||
byte_queue_read_ack(&conn->output, byte_queue_read_buf(&conn->output).len);
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_LINE;
|
||||
|
||||
} else {
|
||||
// Find a free connection slot
|
||||
if (client->num_conns == HTTP_CLIENT_CAPACITY)
|
||||
return -1;
|
||||
|
||||
int i = 0;
|
||||
while (client->conns[i].state != HTTP_CLIENT_CONN_FREE) {
|
||||
i++;
|
||||
if (i >= HTTP_CLIENT_CAPACITY)
|
||||
return -1;
|
||||
}
|
||||
|
||||
conn = &client->conns[i];
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_LINE;
|
||||
conn->gen = 0;
|
||||
conn->handle = SOCKET_HANDLE_INVALID;
|
||||
conn->client = client;
|
||||
byte_queue_init(&conn->input, client->input_buffer_limit);
|
||||
byte_queue_init(&conn->output, client->output_buffer_limit);
|
||||
client->num_conns++;
|
||||
}
|
||||
|
||||
*builder = (HTTP_RequestBuilder) {
|
||||
client,
|
||||
conn - client->conns,
|
||||
conn->gen
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_request_builder_url(HTTP_RequestBuilder builder,
|
||||
HTTP_String url)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
if (conn->state != HTTP_CLIENT_CONN_WAIT_LINE)
|
||||
return;
|
||||
|
||||
// Parse the URL to extract components
|
||||
HTTP_URL parsed_url;
|
||||
if (http_parse_url(url.ptr, url.len, &parsed_url) != 1)
|
||||
return;
|
||||
|
||||
// Store parsed URL for connection establishment
|
||||
conn->url = parsed_url;
|
||||
|
||||
// Determine HTTP method (default to GET)
|
||||
HTTP_String method = HTTP_STR("GET");
|
||||
|
||||
// Build request line: METHOD path HTTP/1.1\r\n
|
||||
byte_queue_write_fmt(&conn->output, "%.*s %.*s HTTP/1.1\r\n",
|
||||
method.len, method.ptr,
|
||||
parsed_url.path.len, parsed_url.path.ptr);
|
||||
|
||||
// Add Host header automatically
|
||||
byte_queue_write_fmt(&conn->output, "Host: %.*s",
|
||||
parsed_url.authority.host.text.len,
|
||||
parsed_url.authority.host.text.ptr);
|
||||
|
||||
if (parsed_url.authority.port > 0) {
|
||||
byte_queue_write_fmt(&conn->output, ":%d", parsed_url.authority.port);
|
||||
}
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_HEADER;
|
||||
}
|
||||
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
if (conn->state != HTTP_CLIENT_CONN_WAIT_HEADER)
|
||||
return;
|
||||
|
||||
// Validate header: must contain a colon and no control characters
|
||||
bool has_colon = false;
|
||||
for (int i = 0; i < str.len; i++) {
|
||||
char c = str.ptr[i];
|
||||
if (c == ':')
|
||||
has_colon = true;
|
||||
// Reject control characters (especially \r and \n)
|
||||
if (c < 0x20 && c != '\t')
|
||||
return;
|
||||
}
|
||||
if (!has_colon)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str.ptr, str.len);
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
}
|
||||
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder,
|
||||
HTTP_String str)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
// Transition from WAIT_HEADER to WAIT_BODY if needed
|
||||
if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) {
|
||||
// End headers section
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
conn->state = HTTP_CLIENT_CONN_WAIT_BODY;
|
||||
}
|
||||
|
||||
if (conn->state != HTTP_CLIENT_CONN_WAIT_BODY)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str.ptr, str.len);
|
||||
}
|
||||
|
||||
int http_request_builder_send(HTTP_RequestBuilder builder)
|
||||
{
|
||||
HTTP_ClientConn *conn = request_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return -1;
|
||||
|
||||
// Finalize the request
|
||||
if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) {
|
||||
// No body, just end headers
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
}
|
||||
|
||||
// Establish connection if not already connected
|
||||
if (conn->handle == SOCKET_HANDLE_INVALID) {
|
||||
|
||||
// Determine if connection should be secure
|
||||
bool secure = false;
|
||||
if (conn->url.scheme.len == 5 &&
|
||||
strncmp(conn->url.scheme.ptr, "https", 5) == 0) {
|
||||
secure = true;
|
||||
}
|
||||
|
||||
// Prepare connection target
|
||||
ConnectTarget target;
|
||||
target.port = conn->url.authority.port;
|
||||
if (target.port <= 0)
|
||||
target.port = secure ? 443 : 80;
|
||||
|
||||
// Set up target based on host type
|
||||
if (conn->url.authority.host.mode == HTTP_HOST_MODE_NAME) {
|
||||
target.type = CONNECT_TARGET_NAME;
|
||||
target.name = (String) {
|
||||
conn->url.authority.host.name.ptr,
|
||||
conn->url.authority.host.name.len
|
||||
};
|
||||
} else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV4) {
|
||||
target.type = CONNECT_TARGET_IPV4;
|
||||
target.ipv4 = (IPv4) { conn->url.authority.host.ipv4.data };
|
||||
} else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV6) {
|
||||
target.type = CONNECT_TARGET_IPV6;
|
||||
for (int i = 0; i < 8; i++)
|
||||
target.ipv6.data[i] = conn->url.authority.host.ipv6.data[i];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (socket_connect(&conn->client->sockets, 1, &target, secure, conn) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
conn->state = HTTP_CLIENT_CONN_FLUSHING;
|
||||
conn->gen++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Look at the input buffer to see if a complete response
|
||||
// was buffered. If it was, change the connection's status
|
||||
// to COMPLETE and push it to the ready queue.
|
||||
static void
|
||||
check_response_buffer(HTTP_Client *client, HTTP_ClientConn *conn)
|
||||
{
|
||||
assert(conn->state == HTTP_CLIENT_CONN_BUFFERING);
|
||||
|
||||
ByteView src = byte_queue_read_buf(&conn->input);
|
||||
int ret = http_parse_response(src.ptr, src.len, &conn->response);
|
||||
|
||||
if (ret < 0) {
|
||||
// Invalid response
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
|
||||
} else if (ret == 0) {
|
||||
// Still waiting
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
|
||||
// If the queue reached its limit and we still didn't receive
|
||||
// a complete response, abort the exchange.
|
||||
if (byte_queue_full(&conn->input))
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
|
||||
} else {
|
||||
// Ready
|
||||
assert(ret == 1);
|
||||
|
||||
conn->state = HTTP_CLIENT_CONN_COMPLETE;
|
||||
conn->response.context = conn;
|
||||
|
||||
// Push to the ready queue
|
||||
assert(client->num_ready < HTTP_CLIENT_CAPACITY);
|
||||
int tail = (client->ready_head + client->num_ready) % HTTP_CLIENT_CAPACITY;
|
||||
client->ready[tail] = conn - client->conns;
|
||||
client->num_ready++;
|
||||
}
|
||||
}
|
||||
|
||||
int http_client_process_events(HTTP_Client *client,
|
||||
EventRegister *reg)
|
||||
{
|
||||
SocketEvent events[HTTP_CLIENT_CAPACITY];
|
||||
int num_events = socket_manager_translate_events(
|
||||
&client->sockets, events, reg);
|
||||
if (num_events < 0)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
|
||||
HTTP_ClientConn *conn = events[i].user;
|
||||
|
||||
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
||||
|
||||
// TODO
|
||||
if (conn != NULL) {
|
||||
http_client_conn_free(conn);
|
||||
conn->state = HTTP_CLIENT_CONN_FREE;
|
||||
client->num_conns--;
|
||||
}
|
||||
|
||||
} else if (events[i].type == SOCKET_EVENT_READY) {
|
||||
|
||||
// TODO
|
||||
if (conn == NULL)
|
||||
continue;
|
||||
|
||||
// Store the handle if this is a new connection
|
||||
if (conn->handle == SOCKET_HANDLE_INVALID)
|
||||
conn->handle = events[i].handle;
|
||||
|
||||
if (conn->state == HTTP_CLIENT_CONN_FLUSHING) {
|
||||
|
||||
// Send request data
|
||||
int num = 0;
|
||||
ByteView src = byte_queue_read_buf(&conn->output);
|
||||
if (src.len)
|
||||
num = socket_send(&client->sockets, conn->handle, src.ptr, src.len);
|
||||
byte_queue_read_ack(&conn->output, num);
|
||||
|
||||
if (byte_queue_error(&conn->output)) {
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
} else if (byte_queue_empty(&conn->output)) {
|
||||
// Request fully sent, now wait for response
|
||||
conn->state = HTTP_CLIENT_CONN_BUFFERING;
|
||||
}
|
||||
|
||||
// TODO
|
||||
} else if (conn->state == HTTP_CLIENT_CONN_BUFFERING) {
|
||||
|
||||
// Receive response data
|
||||
int min_recv = 1<<10;
|
||||
byte_queue_write_setmincap(&conn->input, min_recv);
|
||||
|
||||
int num = 0;
|
||||
ByteView dst = byte_queue_write_buf(&conn->input);
|
||||
if (dst.len)
|
||||
num = socket_recv(&client->sockets, conn->handle, dst.ptr, dst.len);
|
||||
byte_queue_write_ack(&conn->input, num);
|
||||
|
||||
if (byte_queue_error(&conn->input))
|
||||
socket_close(&client->sockets, conn->handle);
|
||||
else
|
||||
check_response_buffer(client, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -112,7 +399,7 @@ bool http_client_next_response(HTTP_Client *client,
|
||||
if (client->num_ready == 0)
|
||||
return false;
|
||||
|
||||
HTTP_ClientConn *conn = &client->conns[client->ready_head];
|
||||
HTTP_ClientConn *conn = &client->conns[client->ready[client->ready_head]];
|
||||
client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY;
|
||||
client->num_ready--;
|
||||
|
||||
@@ -123,5 +410,16 @@ bool http_client_next_response(HTTP_Client *client,
|
||||
|
||||
void http_free_response(HTTP_Response *res)
|
||||
{
|
||||
// TODO
|
||||
if (res == NULL || res->context == NULL)
|
||||
return;
|
||||
|
||||
HTTP_ClientConn *conn = (HTTP_ClientConn*) res->context;
|
||||
|
||||
// Free the connection resources
|
||||
http_client_conn_free(conn);
|
||||
conn->state = HTTP_CLIENT_CONN_FREE;
|
||||
conn->client->num_conns--;
|
||||
|
||||
// Mark response as freed
|
||||
res->context = NULL;
|
||||
}
|
||||
|
||||
+34
-4
@@ -16,18 +16,43 @@ typedef enum {
|
||||
} HTTP_ClientConnState;
|
||||
|
||||
// Fields of this struct are private
|
||||
typedef struct HTTP_Client HTTP_Client;
|
||||
|
||||
typedef struct {
|
||||
HTTP_ClientConnState state;
|
||||
|
||||
// Handle to the socket
|
||||
SocketHandle handle;
|
||||
|
||||
// Pointer back to the client
|
||||
HTTP_Client *client;
|
||||
|
||||
// Generation counter for request builder validation
|
||||
uint16_t gen;
|
||||
|
||||
// Data received from the server
|
||||
ByteQueue input;
|
||||
|
||||
// Data being sent to the server
|
||||
ByteQueue output;
|
||||
|
||||
// Parsed URL for connection establishment
|
||||
HTTP_URL url;
|
||||
|
||||
// Parsed response once complete
|
||||
HTTP_Response response;
|
||||
} HTTP_ClientConn;
|
||||
|
||||
// Fields of this struct are private
|
||||
typedef struct {
|
||||
struct HTTP_Client {
|
||||
|
||||
// Size limit of the input and output buffer of each
|
||||
// connection.
|
||||
uint32_t input_buffer_limit;
|
||||
uint32_t output_buffer_limit;
|
||||
|
||||
// Array of connections. The counter contains the
|
||||
// number of structs such that state=FREE.
|
||||
// number of structs such that state!=FREE.
|
||||
int num_conns;
|
||||
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
@@ -40,14 +65,14 @@ typedef struct {
|
||||
// Asynchronous TCP and TLS socket abstraction
|
||||
SocketManager sockets;
|
||||
|
||||
// The server object doesn't interact with this
|
||||
// The client object doesn't interact with this
|
||||
// field directly, it just initializes the socket
|
||||
// manager with a pointer to it. This allows
|
||||
// allocating the exact number of sockets we
|
||||
// will need.
|
||||
Socket socket_pool[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
} HTTP_Client;
|
||||
};
|
||||
|
||||
// Initialize an HTTP client object. This allows one to
|
||||
// perform a number of requests in parallel.
|
||||
@@ -56,6 +81,11 @@ int http_client_init(HTTP_Client *client);
|
||||
// Release resources associated to a client object.
|
||||
void http_client_free(HTTP_Client *client);
|
||||
|
||||
// Set input and output buffer size limit for any
|
||||
// given connection. The default value is 1MB
|
||||
void http_client_set_input_limit(HTTP_Client *client, uint32_t limit);
|
||||
void http_client_set_output_limit(HTTP_Client *client, uint32_t limit);
|
||||
|
||||
// When a thread is blocked waiting for client events,
|
||||
// other threads can call this function to wake it up.
|
||||
int http_client_wakeup(HTTP_Client *client);
|
||||
|
||||
Reference in New Issue
Block a user