First version of the refactor

This commit is contained in:
2025-11-21 12:53:03 +01:00
parent 7be22fed4b
commit 879dc74e34
55 changed files with 4929 additions and 10370 deletions
+1 -7
View File
@@ -1,9 +1,3 @@
#include <stddef.h>
#include <string.h>
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#endif
bool http_streq(HTTP_String s1, HTTP_String s2)
{
@@ -100,4 +94,4 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
cur++;
}
putc('\n', stream);
}
}
-31
View File
@@ -1,7 +1,3 @@
#ifndef CHTTP_BASIC_INCLUDED
#define CHTTP_BASIC_INCLUDED
#include <stdbool.h>
// String type used throughout cHTTP.
typedef struct {
@@ -52,30 +48,3 @@ void print_bytes(HTTP_String prefix, HTTP_String src);
// TODO: comment
#define HTTP_UNPACK(X) (X).len, (X).ptr
// Macro used to make invariants of the code more explicit.
//
// Say you have some function that operates on two integers
// and that by design their sum is always 100. This macro is
// useful to make that explicit:
//
// void func(int a, int b)
// {
// HTTP_ASSERT(a + b == 100);
// ...
// }
//
// Assertions are about documentation, *not* error management.
//
// In non-release builds (where NDEBUG is not defined) asserted
// expressions are evaluated and if not true, the program is halted.
// This is quite nice as they offer a way to document code in
// a way that can be checked at runtime, unlike regular comments
// like this one.
#ifdef NDEBUG
#define HTTP_ASSERT(X) ((void) 0)
#else
#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }}
#endif
#endif // CHTTP_BASIC_INCLUDED
+258
View File
@@ -0,0 +1,258 @@
void byte_queue_init(ByteQueue *queue, uint32_t limit)
{
queue->flags = 0;
queue->head = 0;
queue->size = 0;
queue->used = 0;
queue->curs = 0;
queue->limit = limit;
queue->data = NULL;
queue->read_target = NULL;
}
// Deinitialize the queue
void byte_queue_free(ByteQueue *queue)
{
if (queue->read_target) {
if (queue->read_target != queue->data)
free(queue->read_target);
queue->read_target = NULL;
queue->read_target_size = 0;
}
free(queue->data);
queue->data = NULL;
}
int byte_queue_error(ByteQueue *queue)
{
return queue->flags & BYTE_QUEUE_ERROR;
}
int byte_queue_empty(ByteQueue *queue)
{
return queue->used == 0;
}
int byte_queue_full(ByteQueue *queue)
{
return queue->used == queue->limit;
}
ByteView byte_queue_read_buf(ByteQueue *queue)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return (ByteView) {NULL, 0};
assert((queue->flags & BYTE_QUEUE_READ) == 0);
queue->flags |= BYTE_QUEUE_READ;
queue->read_target = queue->data;
queue->read_target_size = queue->size;
if (queue->data == NULL)
return (ByteView) {NULL, 0};
return (ByteView) { queue->data + queue->head, queue->used };
}
void byte_queue_read_ack(ByteQueue *queue, uint32_t num)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
if ((queue->flags & BYTE_QUEUE_READ) == 0)
return;
queue->flags &= ~BYTE_QUEUE_READ;
assert((uint32_t) num <= queue->used);
queue->head += (uint32_t) num;
queue->used -= (uint32_t) num;
queue->curs += (uint32_t) num;
if (queue->read_target) {
if (queue->read_target != queue->data)
free(queue->read_target);
queue->read_target = NULL;
queue->read_target_size = 0;
}
}
ByteView byte_queue_write_buf(ByteQueue *queue)
{
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
return (ByteView) {NULL, 0};
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
queue->flags |= BYTE_QUEUE_WRITE;
return (ByteView) {
queue->data + (queue->head + queue->used),
queue->size - (queue->head + queue->used),
};
}
void byte_queue_write_ack(ByteQueue *queue, uint32_t num)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
return;
queue->flags &= ~BYTE_QUEUE_WRITE;
queue->used += num;
}
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap)
{
// Sticky error
if (queue->flags & BYTE_QUEUE_ERROR)
return 0;
// In general, the queue's contents look like this:
//
// size
// v
// [___xxxxxxxxxxxx________]
// ^ ^ ^
// 0 head head + used
//
// This function needs to make sure that at least [mincap]
// bytes are available on the right side of the content.
//
// We have 3 cases:
//
// 1) If there is enough memory already, this function doesn't
// need to do anything.
//
// 2) If there isn't enough memory on the right but there is
// enough free memory if we cound the left unused region,
// then the content is moved back to the
// start of the buffer.
//
// 3) If there isn't enough memory considering both sides, this
// function needs to allocate a new buffer.
//
// If there are pending read or write operations, the application
// is holding pointers to the buffer, so we need to make sure
// to not invalidate them. The only real problem is pending reads
// since this function can only be called before starting a write
// opearation.
//
// To avoid invalidating the read pointer when we allocate a new
// buffer, we don't free the old buffer. Instead, we store the
// pointer in the "old" field so that the read ack function can
// free it.
//
// To avoid invalidating the pointer when we are moving back the
// content since there is enough memory at the start of the buffer,
// we just avoid that. Even if there is enough memory considering
// left and right free regions, we allocate a new buffer.
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
uint32_t total_free_space = queue->size - queue->used;
uint32_t free_space_after_data = queue->size - queue->used - queue->head;
int moved = 0;
if (free_space_after_data < mincap) {
if (total_free_space < mincap || (queue->read_target == queue->data)) {
// Resize required
if (queue->used + mincap > queue->limit) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
}
uint32_t size;
if (queue->size > UINT32_MAX / 2)
size = UINT32_MAX;
else
size = 2 * queue->size;
if (size < queue->used + mincap)
size = queue->used + mincap;
if (size > queue->limit)
size = queue->limit;
uint8_t *data = malloc(size);
if (!data) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
}
if (queue->used > 0)
memcpy(data, queue->data + queue->head, queue->used);
if (queue->read_target != queue->data)
free(queue->data);
queue->data = data;
queue->head = 0;
queue->size = size;
} else {
// Move required
memmove(queue->data, queue->data + queue->head, queue->used);
queue->head = 0;
}
moved = 1;
}
return moved;
}
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len)
{
byte_queue_write_setmincap(queue, len);
ByteView dst = byte_queue_write_buf(queue);
if (dst.ptr) {
memcpy(dst.ptr, ptr, len);
byte_queue_write_ack(queue, len);
}
}
ByteQueueOffset byte_queue_offset(ByteQueue *queue)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return (ByteQueueOffset) { 0 };
return (ByteQueueOffset) { queue->curs + queue->used };
}
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off,
void *src, uint32_t len)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
// Check that the offset is in range
assert(off >= queue->curs && off - queue->curs < queue->used);
// Check that the length is in range
assert(len <= queue->used - (off - queue->curs));
// Perform the patch
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
memcpy(dst, src, len);
}
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off)
{
return queue->curs + queue->used - off;
}
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
uint64_t num = (queue->curs + queue->used) - offset;
assert(num <= queue->used);
queue->used -= num;
}
+134
View File
@@ -0,0 +1,134 @@
// This is the implementation of a byte queue useful
// for systems that need to process engs of bytes.
//
// It features sticky errors, a zero-copy interface,
// and a safe mechanism to patch previously written
// bytes.
//
// Only up to 4GB of data can be stored at once.
// Internal use only
enum {
BYTE_QUEUE_ERROR = 1 << 0,
BYTE_QUEUE_READ = 1 << 1,
BYTE_QUEUE_WRITE = 1 << 2,
};
typedef struct {
uint8_t *ptr;
size_t len;
} ByteView;
// Fields are for internal use only
typedef struct {
uint64_t curs;
uint8_t* data;
uint32_t head;
uint32_t size;
uint32_t used;
uint32_t limit;
uint8_t* read_target;
uint32_t read_target_size;
int flags;
} ByteQueue;
// Represents an offset inside the queue relative
// to the first byte ever appended to the queue,
// therefore consuming bytes from the queue does
// not invalidate this type of offset.
typedef uint64_t ByteQueueOffset;
// Initialize the queue with a given capacity limit.
// This is just a soft limit. The queue will allocate
// dynamically as needed up to this limit and won't
// grow further. When the limit is reached, http_queue_full
// returns true.
void byte_queue_init(ByteQueue *queue, uint32_t limit);
// Free resources associated to this queue
void byte_queue_free(ByteQueue *queue);
// Check whether an error occurred inside the queue
int byte_queue_error(ByteQueue *queue);
// Returns 1 if the queue has no bytes inside it,
// or 0 otherwise.
int byte_queue_empty(ByteQueue *queue);
// Returns 1 if the queue reached its limit, or 0
// otherwise.
int byte_queue_full(ByteQueue *queue);
// These two functions are to be used together.
// read_buf returns a view into the queue of the
// bytes that can be read from it. The caller can
// decide how many of those bytes can be removed
// by passing the count to the read_ack function.
// If an error occurred inside the queue, this
// function returns an empty view.
//
// Note that the calls to read_buf and read_ack
// may be far apart. Other operations won't interfere
// with the read. The only rule is you can't call
// read_buf multiple times before calling read_ack.
ByteView byte_queue_read_buf(ByteQueue *queue);
void byte_queue_read_ack(ByteQueue *queue, uint32_t num);
// Similar to the read_buf/read_ack functions,
// but write_buf returns a view of the unused
// memory inside the queue, and write_ack is
// used to tell the queue how many bytes were
// written into it. Note that to ensure there
// is a minimum amount of free space in the queue,
// the user needs to call byte_queue_setmincap.
// If an error occurred inside the queue, this
// function returns an empty view.
//
// Note that the calls to write_buf and write_ack
// may be far apart. Other operations won't interfere
// with the write (except for other byte_queue_write_*
// functions). The only rule is you can't call
// write_buf multiple times before calling write_ack.
ByteView byte_queue_write_buf(ByteQueue *queue);
void byte_queue_write_ack(ByteQueue *queue, uint32_t num);
// Sets the minimum capacity for the next write
// operation and returns 1 if the content of the
// queue was moved, else 0 is returned.
//
// You must not call this function while a write
// is pending. In other words, you must do this:
//
// byte_queue_write_setmincap(queue, mincap);
// dst = byte_queue_write_buf(queue, &cap);
// ...
// byte_queue_write_ack(num);
//
// And NOT this:
//
// dst = byte_queue_write_buf(queue);
// byte_queue_write_setmincap(queue, mincap); <-- BAD
// ...
// byte_queue_write_ack(num);
//
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap);
// Write some bytes to the queue. This is a
// short hand for write_buf/memcpy/write_ack
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len);
// Returns the current offset inside the queue
ByteQueueOffset byte_queue_offset(ByteQueue *queue);
// Writes some bytes at the specified offset. It's
// the responsibility of the user to make sure that
// the offset still refers to content inside the queue.
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len);
// Returns the number of bytes from the given offset
// to the end of the queue.
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off);
// Removes all bytes from the given offset to the the
// end of the queue.
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset);
+3 -19
View File
@@ -1,19 +1,3 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HTTPS_ENABLED
#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/x509v3.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bn.h>
#endif
#ifndef HTTP_AMALGAMATION
#include "cert.h"
#endif
#ifdef HTTPS_ENABLED
@@ -51,10 +35,10 @@ static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HT
// Set version (version 3)
X509_set_version(x509, 2);
// Set serial number
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
// Set validity period
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year
@@ -166,4 +150,4 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
return -1;
}
#endif
#endif
-9
View File
@@ -1,10 +1,3 @@
#ifndef CERT_INCLUDED
#define CERT_INCLUDED
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#endif
// This is an utility to create self-signed certificates
// useful when testing HTTPS servers locally. This is only
// meant to be used by people starting out with a library
@@ -21,5 +14,3 @@
// key file with the key used to sign the certificate.
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
HTTP_String cert_file, HTTP_String key_file);
#endif // CERT_INCLUDED
+67 -408
View File
@@ -1,453 +1,112 @@
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#define POLL WSAPoll
#endif
#ifdef __linux__
#include <poll.h>
#define POLL poll
#endif
#ifndef HTTP_AMALGAMATION
#include "client.h"
#include "engine.h"
#include "socket_pool.h"
#endif
#define CLIENT_MAX_CONNS 256
typedef enum {
CLIENT_CONNECTION_FREE,
CLIENT_CONNECTION_INIT,
CLIENT_CONNECTION_INIT_ERROR,
CLIENT_CONNECTION_WAIT,
CLIENT_CONNECTION_DONE,
} ClientConnectionState;
typedef struct {
ClientConnectionState state;
uint16_t gen;
SocketHandle sock;
HTTP_Engine eng;
bool trace;
void* user_data;
} ClientConnection;
struct HTTP_Client {
SocketPool *socket_pool;
int num_conns;
ClientConnection conns[CLIENT_MAX_CONNS];
int ready_head;
int ready_count;
int ready[CLIENT_MAX_CONNS];
};
int http_global_init(void)
int http_client_init(HTTP_Client *client)
{
int ret = socket_pool_global_init();
if (ret < 0)
client->num_conns = 0;
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++)
client->conns[i].state = HTTP_CLIENT_CONN_FREE;
client->num_ready = 0;
client->ready_head = 0;
if (socket_manager_init(&client->sockets,
client->socket_pool, HTTP_CLIENT_CAPACITY) < 0)
return -1;
return 0;
}
void http_global_free(void)
{
socket_pool_global_free();
}
// Rename the memory function
static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
(void)data;
switch (tag) {
case HTTP_MEMFUNC_MALLOC:
return malloc(len);
case HTTP_MEMFUNC_FREE:
free(ptr);
return NULL;
}
return NULL;
}
HTTP_Client *http_client_init(void)
{
HTTP_Client *client = malloc(sizeof(HTTP_Client));
if (client == NULL)
return NULL;
int max_socks = 100;
SocketPool *socket_pool = socket_pool_init(HTTP_STR(""), 0, 0, max_socks, false, 0, HTTP_STR(""), HTTP_STR(""));
if (socket_pool == NULL) {
free(client);
return NULL;
}
client->socket_pool = socket_pool;
for (int i = 0; i < CLIENT_MAX_CONNS; i++) {
client->conns[i].state = CLIENT_CONNECTION_FREE;
client->conns[i].gen = 1;
}
client->num_conns = 0;
client->ready_head = 0;
client->ready_count = 0;
return client;
}
void http_client_free(HTTP_Client *client)
{
for (int i = 0, j = 0; j < client->num_conns; i++) {
socket_manager_free(&client->sockets);
if (client->conns[i].state == CLIENT_CONNECTION_FREE)
for (int i = 0, j = 0; j < client->num_conns; i++) {
HTTP_ClientConn *conn = &client->conns[i];
if (conn->state == HTTP_CLIENT_CONN_FREE)
continue;
j++;
// TODO
http_client_conn_free(conn);
}
}
socket_pool_free(client->socket_pool);
free(client);
int http_client_wakeup(HTTP_Client *client)
{
if (socket_manager_wakeup(&client->sockets) < 0)
return -1;
return 0;
}
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder)
{
if (client->num_conns == CLIENT_MAX_CONNS)
return -1;
int i = 0;
while (client->conns[i].state != CLIENT_CONNECTION_FREE)
i++;
client->conns[i].sock = -1;
client->conns[i].user_data = NULL;
client->conns[i].trace = false;
client->conns[i].state = CLIENT_CONNECTION_INIT;
http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL);
client->num_conns++;
*builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen };
return 0;
// TODO
}
int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data)
void http_request_builder_line(HTTP_RequestBuilder builder, xxxx)
{
while (client->ready_count == 0) {
// TODO
}
SocketEvent event = socket_pool_wait(client->socket_pool);
switch (event.type) {
void http_request_builder_header(HTTP_RequestBuilder builder, String str)
{
// TODO
}
case SOCKET_EVENT_DIED:
{
ClientConnection *conn = event.user_data;
conn->state = CLIENT_CONNECTION_DONE;
void http_request_builder_body(HTTP_RequestBuilder builder, String str)
{
// TODO
}
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
client->ready[tail] = conn - client->conns;
client->ready_count++;
}
break;
int http_request_builder_send(HTTP_RequestBuilder builder)
{
// TODO
}
case SOCKET_EVENT_READY:
{
ClientConnection *conn = event.user_data;
int http_client_register_events(HTTP_Client *client,
struct pollfd *polled, int max_polled)
{
return socket_manager_register_events(
&client->sockets, polled, max_polled);
}
if (conn->sock == -1)
conn->sock = event.handle;
int http_client_process_events(HTTP_Client *client,
struct pollfd *polled, int num_polled)
{
SocketEvent events[HTTP_CLIENT_CAPACITY];
int num_events = socket_manager_translate_events(
&client->sockets, polled, num_polled);
HTTP_EngineState engine_state;
engine_state = http_engine_state(&conn->eng);
for (int i = 0; i < num_events; i++) {
if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) {
int len;
char *buf;
buf = http_engine_recvbuf(&conn->eng, &len);
if (buf) {
int ret = socket_pool_read(client->socket_pool, conn->sock, buf, len);
if (conn->trace)
print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret });
http_engine_recvack(&conn->eng, ret);
}
} else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) {
int len;
char *buf;
buf = http_engine_sendbuf(&conn->eng, &len);
if (buf) {
int ret = socket_pool_write(client->socket_pool, conn->sock, buf, len);
if (conn->trace)
print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret });
http_engine_sendack(&conn->eng, ret);
}
}
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
engine_state = http_engine_state(&conn->eng);
// TODO
if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
socket_pool_close(client->socket_pool, conn->sock);
}
break;
} else if (events[i].type == SOCKET_EVENT_READY) {
case SOCKET_EVENT_ERROR:
return -1;
case SOCKET_EVENT_SIGNAL:
return 1;
// TODO
}
}
int index = client->ready[client->ready_head];
client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS;
client->ready_count--;
ClientConnection *conn = &client->conns[index];
HTTP_Response *result2 = http_engine_getres(&conn->eng);
if (result)
*result = result2;
if (user_data)
*user_data = conn->user_data;
if (result2 == NULL) {
http_engine_free(&conn->eng);
conn->state = CLIENT_CONNECTION_FREE;
client->num_conns--;
} else {
result2->context = client;
// TODO
}
return 0;
}
static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle)
bool http_client_next_response(HTTP_Client *client,
HTTP_Response **response);
{
if (handle.data0 == NULL)
return NULL;
if (server->num_ready == 0)
return false;
HTTP_Client *client = handle.data0;
HTTP_ClientConn *conn = &client->conns[client->ready_head];
client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY;
client->num_ready--;
if (handle.data1 >= CLIENT_MAX_CONNS)
return NULL;
ClientConnection *conn = &client->conns[handle.data1];
if (handle.data2 != conn->gen)
return NULL;
return conn;
assert(conn->state == HTTP_CLIENT_CONN_COMPLETE);
*response = &conn->response;
return true;
}
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data)
void http_free_response(HTTP_Response *res)
{
ClientConnection *conn = client_builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != CLIENT_CONNECTION_INIT)
return;
conn->user_data = user_data;
// TODO
}
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace)
{
ClientConnection *conn = client_builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != CLIENT_CONNECTION_INIT)
return;
conn->trace = trace;
}
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url)
{
ClientConnection *conn = client_builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != CLIENT_CONNECTION_INIT)
return;
HTTP_Client *client = builder.data0;
HTTP_URL parsed_url;
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
if (ret != url.len) {
conn->state = CLIENT_CONNECTION_INIT_ERROR;
return;
}
bool secure = false;
if (http_streq(parsed_url.scheme, HTTP_STR("https"))) {
secure = true;
} else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) {
conn->state = CLIENT_CONNECTION_INIT_ERROR;
return;
}
int port = parsed_url.authority.port;
if (port == 0) {
if (secure)
port = 443;
else
port = 80;
}
switch (parsed_url.authority.host.mode) {
case HTTP_HOST_MODE_IPV4: ret = socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn); break;
case HTTP_HOST_MODE_IPV6: ret = socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn); break;
case HTTP_HOST_MODE_NAME: ret = socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn); break;
case HTTP_HOST_MODE_VOID: ret = -1; return;
}
if (ret < 0) {
conn->state = CLIENT_CONNECTION_INIT_ERROR;
return;
}
http_engine_url(&conn->eng, method, url, 1);
}
void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str)
{
ClientConnection *conn = client_builder_to_conn(handle);
if (conn == NULL)
return;
if (conn->state != CLIENT_CONNECTION_INIT)
return;
http_engine_header(&conn->eng, str);
}
void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str)
{
ClientConnection *conn = client_builder_to_conn(handle);
if (conn == NULL)
return;
if (conn->state != CLIENT_CONNECTION_INIT)
return;
http_engine_body(&conn->eng, str);
}
void http_request_builder_submit(HTTP_RequestBuilder handle)
{
HTTP_Client *client = handle.data0;
ClientConnection *conn = client_builder_to_conn(handle);
if (conn == NULL)
return;
if (conn->state != CLIENT_CONNECTION_INIT &&
conn->state != CLIENT_CONNECTION_INIT_ERROR)
return;
// TODO: invalidate the handle
if (conn->state == CLIENT_CONNECTION_INIT_ERROR) {
conn->state = CLIENT_CONNECTION_DONE;
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
client->ready[tail] = conn - client->conns;
client->ready_count++;
} else {
http_engine_done(&conn->eng);
conn->state = CLIENT_CONNECTION_WAIT;
}
}
void http_response_free(HTTP_Response *res)
{
if (res == NULL)
return;
HTTP_Client *client = res->context;
ClientConnection *conn = NULL;
for (int i = 0, j = 0; j < client->num_conns; i++) {
if (client->conns[i].state == CLIENT_CONNECTION_FREE)
continue;
j++;
if (client->conns[i].state != CLIENT_CONNECTION_DONE)
continue;
if (http_engine_getres(&client->conns[i].eng) == res) {
conn = &client->conns[i];
break;
}
}
HTTP_ASSERT(conn);
http_engine_free(&conn->eng);
conn->state = CLIENT_CONNECTION_FREE;
client->num_conns--;
}
static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called
static HTTP_Client *get_default_client(void)
{
if (default_client___ == NULL)
default_client___ = http_client_init();
return default_client___;
}
HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers)
{
HTTP_Client *client = get_default_client();
if (client == NULL)
return NULL;
HTTP_RequestBuilder builder;
int ret = http_client_get_builder(client, &builder);
if (ret < 0)
return NULL;
http_request_builder_line(builder, HTTP_METHOD_GET, url);
for (int i = 0; i < num_headers; i++)
http_request_builder_header(builder, headers[i]);
http_request_builder_submit(builder);
HTTP_Response *res;
ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending
if (ret < 0)
return NULL;
return res;
}
HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body)
{
HTTP_Client *client = get_default_client();
if (client == NULL)
return NULL;
HTTP_RequestBuilder builder;
int ret = http_client_get_builder(client, &builder);
if (ret < 0)
return NULL;
http_request_builder_line(builder, HTTP_METHOD_POST, url);
for (int i = 0; i < num_headers; i++)
http_request_builder_header(builder, headers[i]);
http_request_builder_body(builder, body);
http_request_builder_submit(builder);
HTTP_Response *res;
ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending
if (ret < 0)
return NULL;
return res;
}
+99 -79
View File
@@ -1,98 +1,118 @@
#ifndef CLIENT_INCLUDED
#define CLIENT_INCLUDED
#include <stdbool.h>
#ifndef HTTP_AMALGAMATION
#include "parse.h"
#ifndef HTTP_CLIENT_CAPACITY
// The maximum ammount of requests that can be performed
// in parallel.
#define HTTP_CLIENT_CAPACITY (1<<7)
#endif
// Initialize the global state of cHTTP.
//
// cHTTP tries to avoid global state. What this function
// does is call the global initialization functions of
// its dependencies (OpenSSL and Winsock)
int http_global_init(void);
typedef enum {
HTTP_CLIENT_CONN_FREE,
HTTP_CLIENT_CONN_WAIT_LINE,
HTTP_CLIENT_CONN_WAIT_HEADER,
HTTP_CLIENT_CONN_WAIT_BODY,
HTTP_CLIENT_CONN_FLUSHING,
HTTP_CLIENT_CONN_BUFFERING,
HTTP_CLIENT_CONN_COMPLETE,
} HTTP_ClientConnState;
// Free the global state of cHTTP.
void http_global_free(void);
// Opaque type describing an "HTTP client". Any request
// that is started must always be associated to an HTTP
// client object.
typedef struct HTTP_Client HTTP_Client;
// Handle for a pending request. This should be considered
// opaque. Don't read or modify its fields!
// Fields of this struct are private
typedef struct {
void *data0;
int data1;
int data2;
} HTTP_RequestBuilder;
HTTP_ClientConnState state;
ByteQueue input;
ByteQueue output;
} HTTP_ClientConn;
// Initialize a client object. If something goes wrong,
// NULL is returned.
HTTP_Client *http_client_init(void);
// Fields of this struct are private
typedef struct {
// Deinitialize a client object
// Array of connections. The counter contains the
// number of structs such that state=FREE.
int num_conns;
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
// Queue of indices referring to connections that
// are in the COMPLETE state.
int num_ready;
int ready_head;
int ready[HTTP_CLIENT_CAPACITY];
// Asynchronous TCP and TLS socket abstraction
SocketManager sockets;
// The server 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.
int http_client_init(HTTP_Client *client);
// Release resources associated to a client object.
void http_client_free(HTTP_Client *client);
// Create a request object associated to the given client.
// On success, 0 is returned and the handle is initialized.
// On error, -1 is returned.
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder);
// 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);
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data);
typedef struct {
HTTP_Client *client;
uint16_t index;
uint16_t gen;
} HTTP_RequestBuilder;
// Enable/disable I/O tracing for the specified request.
// This must be done when the request is in the initialization
// phase.
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace);
// Create a new request builder object. If the response
// pointer is NULL, a brand new builder is created. If
// response isn't NULL (and http_free_response wasn't
// called on it yet), the connection associated to that
// previous exchange is reused. Note that it's up to the
// user to make sure the requests are targeting the same
// host. Returns 0 on success, -1 on error.
int http_client_get_builder(HTTP_Client *client,
HTTP_Response *response, HTTP_RequestBuilder *builder);
// Set the method and URL of the specified request object.
// This must be the first thing you do after http_client_request
// is called (you may http_request_trace before, but nothing
// else!)
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url);
// Set the URL of the current request. This is the first
// function of the request builder that the user must call.
void http_request_builder_url(HTTP_RequestBuilder builder, String url);
// Append a header to the specified request. You must call
// this after http_request_line and may do so multiple times.
void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str);
// After the URL, the user may set zero or more headers.
void http_request_builder_header(HTTP_RequestBuilder builder, String str);
// Append some data to the request's body. You must call
// this after either http_request_line or http_request_header.
void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str);
// Append bytes to the request's body. You can call this
// any amount of times, as long as it's after having set
// the URL.
void http_request_builder_body(HTTP_RequestBuilder builder, String str);
// Mark the initialization of the request as completed and
// perform the request.
void http_request_builder_submit(HTTP_RequestBuilder builder);
// Mark this request as complete. This invalidates the
// builder.
void http_request_builder_send(HTTP_RequestBuilder builder);
// Free resources associated to a request. This must be called
// after the request has completed.
//
// TODO: allow aborting pending requests
void http_response_free(HTTP_Response *res);
// List all low-level socket events the client is
// waiting for such that the caller can call poll()
// with it.
int http_client_register_events(HTTP_Client *client,
struct pollfd *polled, int max_polled);
// Wait for the completion of one request associated to
// the client. The handle of the resolved request is returned
// through the handle output parameter. If you're not
// interested in which request completed (like when you
// have only one pending request), you can pass NULL.
//
// On error -1 is retutned, else 0 is returned and the
// handle is initialized.
//
// Note that calling this function when no requests are
// pending is considered an error.
int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data);
// The caller has waited for poll() to return and some
// I/O events to be triggered, so now the HTTP client
// can continue its buffering and flushing operations.
int http_client_process_events(HTTP_Client *client,
struct pollfd *polled, int num_polled);
// TODO: comment
HTTP_Response *http_get(HTTP_String url,
HTTP_String *headers, int num_headers);
// After some I/O events were processes, some responses
// may be availabe. This function returns one of the
// buffered responses. If a request was available, true
// is returned. If no more are avaiable, false is returned.
// The returned response must either be freed using the
// http_free_response function or reused by passing it
// to http_client_get_builder.
bool http_client_next_response(HTTP_Client *client,
HTTP_Response **response);
// TODO: comment
HTTP_Response *http_post(HTTP_String url,
HTTP_String *headers, int num_headers,
HTTP_String body);
#endif // CLIENT_INCLUDED
// Free a response object. You can't access its fields
// again after this.
void http_free_response(HTTP_Response *res);
-988
View File
@@ -1,988 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h> // TODO: remove some of these headers
#include <stddef.h>
#include <limits.h>
#include <stdint.h>
#include <string.h>
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#include "engine.h"
#endif
// This is the implementation of a byte queue useful
// for systems that need to process engs of bytes.
//
// It features sticky errors, a zero-copy interface,
// and a safe mechanism to patch previously written
// bytes.
//
// Only up to 4GB of data can be stored at once.
enum {
BYTE_QUEUE_ERROR = 1 << 0,
BYTE_QUEUE_READ = 1 << 1,
BYTE_QUEUE_WRITE = 1 << 2,
};
static void*
callback_malloc(HTTP_ByteQueue *queue, int len)
{
return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata);
}
static void
callback_free(HTTP_ByteQueue *queue, void *ptr, int len)
{
queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata);
}
// Initialize the queue
static void
byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata)
{
queue->flags = 0;
queue->head = 0;
queue->size = 0;
queue->used = 0;
queue->curs = 0;
queue->limit = limit;
queue->data = NULL;
queue->read_target = NULL;
queue->memfunc = memfunc;
queue->memfuncdata = memfuncdata;
}
// Deinitialize the queue
static void
byte_queue_free(HTTP_ByteQueue *queue)
{
if (queue->read_target) {
if (queue->read_target != queue->data)
callback_free(queue, queue->read_target, queue->read_target_size);
queue->read_target = NULL;
queue->read_target_size = 0;
}
callback_free(queue, queue->data, queue->size);
queue->data = NULL;
}
static int
byte_queue_error(HTTP_ByteQueue *queue)
{
return queue->flags & BYTE_QUEUE_ERROR;
}
static int
byte_queue_empty(HTTP_ByteQueue *queue)
{
return queue->used == 0;
}
// Start a read operation on the queue.
//
// This function returnes the pointer to the memory region containing the bytes
// to read. Callers can't read more than [*len] bytes from it. To complete the
// read, the [byte_queue_read_ack] function must be called with the number of
// bytes that were acknowledged by the caller.
//
// Note:
// - You can't have more than one pending read.
static char*
byte_queue_read_buf(HTTP_ByteQueue *queue, int *len)
{
if (queue->flags & BYTE_QUEUE_ERROR) {
*len = 0;
return NULL;
}
HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0);
queue->flags |= BYTE_QUEUE_READ;
queue->read_target = queue->data;
queue->read_target_size = queue->size;
*len = queue->used;
if (queue->data == NULL)
return NULL;
return queue->data + queue->head;
}
// Complete a previously started operation on the queue.
static void
byte_queue_read_ack(HTTP_ByteQueue *queue, int num)
{
HTTP_ASSERT(num >= 0);
if (queue->flags & BYTE_QUEUE_ERROR)
return;
if ((queue->flags & BYTE_QUEUE_READ) == 0)
return;
queue->flags &= ~BYTE_QUEUE_READ;
HTTP_ASSERT((unsigned int) num <= queue->used);
queue->head += (unsigned int) num;
queue->used -= (unsigned int) num;
queue->curs += (unsigned int) num;
if (queue->read_target) {
if (queue->read_target != queue->data)
callback_free(queue, queue->read_target, queue->read_target_size);
queue->read_target = NULL;
queue->read_target_size = 0;
}
}
static char*
byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap)
{
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) {
*cap = 0;
return NULL;
}
HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
queue->flags |= BYTE_QUEUE_WRITE;
unsigned int ucap = queue->size - (queue->head + queue->used);
if (ucap > INT_MAX) ucap = INT_MAX;
*cap = (int) ucap;
return queue->data + (queue->head + queue->used);
}
static void
byte_queue_write_ack(HTTP_ByteQueue *queue, int num)
{
HTTP_ASSERT(num >= 0);
if (queue->flags & BYTE_QUEUE_ERROR)
return;
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
return;
queue->flags &= ~BYTE_QUEUE_WRITE;
queue->used += (unsigned int) num;
}
// Sets the minimum capacity for the next write operation
// and returns 1 if the content of the queue was moved, else
// 0 is returned.
//
// You must not call this function while a write is pending.
// In other words, you must do this:
//
// byte_queue_write_setmincap(queue, mincap);
// dst = byte_queue_write_buf(queue, &cap);
// ...
// byte_queue_write_ack(num);
//
// And NOT this:
//
// dst = byte_queue_write_buf(queue, &cap);
// byte_queue_write_setmincap(queue, mincap); <-- BAD
// ...
// byte_queue_write_ack(num);
//
static int
byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap)
{
HTTP_ASSERT(mincap >= 0);
unsigned int umincap = (unsigned int) mincap;
// Sticky error
if (queue->flags & BYTE_QUEUE_ERROR)
return 0;
// In general, the queue's contents look like this:
//
// size
// v
// [___xxxxxxxxxxxx________]
// ^ ^ ^
// 0 head head + used
//
// This function needs to make sure that at least [mincap]
// bytes are available on the right side of the content.
//
// We have 3 cases:
//
// 1) If there is enough memory already, this function doesn't
// need to do anything.
//
// 2) If there isn't enough memory on the right but there is
// enough free memory if we cound the left unused region,
// then the content is moved back to the
// start of the buffer.
//
// 3) If there isn't enough memory considering both sides, this
// function needs to allocate a new buffer.
//
// If there are pending read or write operations, the application
// is holding pointers to the buffer, so we need to make sure
// to not invalidate them. The only real problem is pending reads
// since this function can only be called before starting a write
// opearation.
//
// To avoid invalidating the read pointer when we allocate a new
// buffer, we don't free the old buffer. Instead, we store the
// pointer in the "old" field so that the read ack function can
// free it.
//
// To avoid invalidating the pointer when we are moving back the
// content since there is enough memory at the start of the buffer,
// we just avoid that. Even if there is enough memory considering
// left and right free regions, we allocate a new buffer.
HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
unsigned int total_free_space = queue->size - queue->used;
unsigned int free_space_after_data = queue->size - queue->used - queue->head;
int moved = 0;
if (free_space_after_data < umincap) {
if (total_free_space < umincap || (queue->read_target == queue->data)) {
// Resize required
if (queue->used + umincap > queue->limit) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
}
unsigned int size;
if (queue->size > UINT32_MAX / 2)
size = UINT32_MAX;
else
size = 2 * queue->size;
if (size < queue->used + umincap)
size = queue->used + umincap;
if (size > queue->limit)
size = queue->limit;
char *data = callback_malloc(queue, size);
if (!data) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
}
if (queue->used > 0)
memcpy(data, queue->data + queue->head, queue->used);
if (queue->read_target != queue->data)
callback_free(queue, queue->data, queue->size);
queue->data = data;
queue->head = 0;
queue->size = size;
} else {
// Move required
memmove(queue->data, queue->data + queue->head, queue->used);
queue->head = 0;
}
moved = 1;
}
return moved;
}
static HTTP_ByteQueueOffset
byte_queue_offset(HTTP_ByteQueue *queue)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return (HTTP_ByteQueueOffset) { 0 };
return (HTTP_ByteQueueOffset) { queue->curs + queue->used };
}
static unsigned int
byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off)
{
return queue->curs + queue->used - off;
}
static void
byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off,
char *src, unsigned int len)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
// Check that the offset is in range
HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used);
// Check that the length is in range
HTTP_ASSERT(len <= queue->used - (off - queue->curs));
// Perform the patch
char *dst = queue->data + queue->head + (off - queue->curs);
memcpy(dst, src, len);
}
static void
byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
unsigned long long num = (queue->curs + queue->used) - offset;
HTTP_ASSERT(num <= queue->used);
queue->used -= num;
}
static void
byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len)
{
if (str == NULL) str = "";
if (len < 0) len = strlen(str);
int cap;
byte_queue_write_setmincap(queue, len);
char *dst = byte_queue_write_buf(queue, &cap);
if (dst) memcpy(dst, str, len);
byte_queue_write_ack(queue, len);
}
static void
byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
va_list args2;
va_copy(args2, args);
int cap;
byte_queue_write_setmincap(queue, 128);
char *dst = byte_queue_write_buf(queue, &cap);
int len = vsnprintf(dst, cap, fmt, args);
if (len < 0) {
queue->flags |= BYTE_QUEUE_ERROR;
va_end(args2);
return;
}
if (len > cap) {
byte_queue_write_ack(queue, 0);
byte_queue_write_setmincap(queue, len+1);
dst = byte_queue_write_buf(queue, &cap);
vsnprintf(dst, cap, fmt, args2);
}
byte_queue_write_ack(queue, len);
va_end(args2);
}
static void
byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
byte_queue_write_fmt2(queue, fmt, args);
va_end(args);
}
#define TEN_SPACES " "
void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata)
{
if (client)
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL;
else
eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF;
eng->closing = 0;
eng->numexch = 0;
byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata);
byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata);
}
void http_engine_free(HTTP_Engine *eng)
{
byte_queue_free(&eng->input);
byte_queue_free(&eng->output);
eng->state = HTTP_ENGINE_STATE_NONE;
}
void http_engine_close(HTTP_Engine *eng)
{
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
else
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
}
HTTP_EngineState http_engine_state(HTTP_Engine *eng)
{
return eng->state;
}
const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove
switch (state) {
case HTTP_ENGINE_STATE_NONE: return "NONE";
case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL";
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER";
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR";
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF";
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK";
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF";
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK";
case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY";
case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED";
case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF";
case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK";
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS";
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER";
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR";
case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF";
case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK";
case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED";
default: return "UNKNOWN";
}
}
char *http_engine_recvbuf(HTTP_Engine *eng, int *cap)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) {
*cap = 0;
return NULL;
}
eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF;
eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK;
byte_queue_write_setmincap(&eng->input, 1<<9);
if (byte_queue_error(&eng->input)) {
*cap = 0;
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
else
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
return NULL;
}
return byte_queue_write_buf(&eng->input, cap);
}
static int
should_keep_alive(HTTP_Engine *eng)
{
HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP);
#if 0
// If the parent system doesn't want us to reuse
// the connection, we certainly can't keep alive.
if ((eng->state & TINYHTTP_STREAM_REUSE) == 0)
return 0;
#endif
if (eng->numexch >= 100) // TODO: Make this a parameter
return 0;
HTTP_Request *req = &eng->result.req;
// If the client is using HTTP/1.0, we can't
// keep alive.
if (req->minor == 0)
return 0;
// TODO: This assumes "Connection" can only hold a single token,
// but this is not true.
int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection"));
if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close")))
return 0;
return 1;
}
static void process_incoming_request(HTTP_Engine *eng)
{
HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK
|| eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR);
char *src;
int len;
src = byte_queue_read_buf(&eng->input, &len);
int ret = http_parse_request(src, len, &eng->result.req);
if (ret == 0) {
byte_queue_read_ack(&eng->input, 0);
eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF;
return;
}
if (ret < 0) {
byte_queue_read_ack(&eng->input, 0);
byte_queue_write(&eng->output,
"HTTP/1.1 400 Bad Request\r\n"
"Connection: Close\r\n"
"Content-Length: 0\r\n"
"\r\n", -1
);
if (byte_queue_error(&eng->output))
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
else {
eng->closing = 1;
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
}
return;
}
HTTP_ASSERT(ret > 0);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS;
eng->reqsize = ret;
eng->keepalive = should_keep_alive(eng);
eng->response_offset = byte_queue_offset(&eng->output);
}
void http_engine_recvack(HTTP_Engine *eng, int num)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0)
return;
byte_queue_write_ack(&eng->input, num);
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
char *src;
int len;
src = byte_queue_read_buf(&eng->input, &len);
int ret = http_parse_response(src, len, &eng->result.res);
if (ret == 0) {
byte_queue_read_ack(&eng->input, 0);
eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF;
return;
}
if (ret < 0) {
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
return;
}
HTTP_ASSERT(ret > 0);
eng->state = HTTP_ENGINE_STATE_CLIENT_READY;
} else {
process_incoming_request(eng);
}
}
char *http_engine_sendbuf(HTTP_Engine *eng, int *len)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) {
*len = 0;
return NULL;
}
eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF;
eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK;
return byte_queue_read_buf(&eng->output, len);
}
void http_engine_sendack(HTTP_Engine *eng, int num)
{
if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK &&
eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK)
return;
byte_queue_read_ack(&eng->output, num);
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
if (byte_queue_empty(&eng->output))
eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF;
else
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
} else {
if (byte_queue_empty(&eng->output)) {
if (!eng->closing && eng->keepalive)
process_incoming_request(eng);
else
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
} else
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
}
}
HTTP_Request *http_engine_getreq(HTTP_Engine *eng)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0)
return NULL;
return &eng->result.req;
}
HTTP_Response *http_engine_getres(HTTP_Engine *eng)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0)
return NULL;
return &eng->result.res;
}
void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor)
{
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL)
return;
eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients
HTTP_URL parsed_url;
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
if (ret != url.len) {
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR;
return;
}
HTTP_String method_and_space = HTTP_STR("???");
switch (method) {
case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break;
case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break;
case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break;
case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break;
case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break;
case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break;
case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break;
case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break;
case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break;
}
HTTP_String path = parsed_url.path;
if (path.len == 0)
path = HTTP_STR("/");
byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len);
byte_queue_write(&eng->output, path.ptr, path.len);
byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len);
byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1);
byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len);
if (parsed_url.authority.port > 0)
byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port);
byte_queue_write(&eng->output, "\r\n", 2);
eng->keepalive = 1; // TODO
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER;
}
static const char*
get_status_text(int code)
{
switch(code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 208: return "Already Reported";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 306: return "Switch Proxy";
case 307: return "Temporary Redirect";
case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 418: return "I'm a teapot";
case 420: return "Enhance your calm";
case 422: return "Unprocessable Entity";
case 426: return "Upgrade Required";
case 429: return "Too many requests";
case 431: return "Request Header Fields Too Large";
case 449: return "Retry With";
case 451: return "Unavailable For Legal Reasons";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
case 509: return "Bandwidth Limit Exceeded";
}
return "???";
}
void http_engine_status(HTTP_Engine *eng, int status)
{
if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS)
return;
byte_queue_write_fmt(&eng->output,
"HTTP/1.1 %d %s\r\n",
status, get_status_text(status));
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER;
}
void http_engine_header(HTTP_Engine *eng, HTTP_String str)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
return;
// TODO: Check that the header is valid
byte_queue_write(&eng->output, str.ptr, str.len);
byte_queue_write(&eng->output, "\r\n", 2);
}
void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
return;
// TODO: Check that the header is valid
byte_queue_write_fmt2(&eng->output, fmt, args);
byte_queue_write(&eng->output, "\r\n", 2);
}
void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
http_engine_header_fmt2(eng, fmt, args);
va_end(args);
}
static void
complete_message_head(HTTP_Engine *eng)
{
if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1);
else byte_queue_write(&eng->output, "Connection: Close\r\n", -1);
byte_queue_write(&eng->output, "Content-Length: ", -1);
eng->content_length_value_offset = byte_queue_offset(&eng->output);
byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1);
byte_queue_write(&eng->output, "\r\n", -1);
eng->content_length_offset = byte_queue_offset(&eng->output);
}
static void complete_message_body(HTTP_Engine *eng)
{
unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset);
if (content_length > UINT32_MAX) {
// TODO
}
char tmp[10];
tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000;
tmp[1] = '0' + content_length / 100000000; content_length %= 100000000;
tmp[2] = '0' + content_length / 10000000; content_length %= 10000000;
tmp[3] = '0' + content_length / 1000000; content_length %= 1000000;
tmp[4] = '0' + content_length / 100000; content_length %= 100000;
tmp[5] = '0' + content_length / 10000; content_length %= 10000;
tmp[6] = '0' + content_length / 1000; content_length %= 1000;
tmp[7] = '0' + content_length / 100; content_length %= 100;
tmp[8] = '0' + content_length / 10; content_length %= 10;
tmp[9] = '0' + content_length;
int i = 0;
while (i < 9 && tmp[i] == '0')
i++;
byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i);
}
void http_engine_body(HTTP_Engine *eng, HTTP_String str)
{
http_engine_bodycap(eng, str.len);
int cap;
char *buf = http_engine_bodybuf(eng, &cap);
if (buf) {
memcpy(buf, str.ptr, str.len);
http_engine_bodyack(eng, str.len);
}
}
static void ensure_body_entered(HTTP_Engine *eng)
{
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
}
} else {
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
}
}
}
void http_engine_bodycap(HTTP_Engine *eng, int mincap)
{
ensure_body_entered(eng);
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
return;
byte_queue_write_setmincap(&eng->output, mincap);
}
char *http_engine_bodybuf(HTTP_Engine *eng, int *cap)
{
ensure_body_entered(eng);
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) {
*cap = 0;
return NULL;
}
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK;
else
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK;
return byte_queue_write_buf(&eng->output, cap);
}
void http_engine_bodyack(HTTP_Engine *eng, int num)
{
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK)
return;
byte_queue_write_ack(&eng->output, num);
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
else
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
}
void http_engine_done(HTTP_Engine *eng)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
return;
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) {
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
return;
}
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
}
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF)
complete_message_body(eng);
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) {
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
return;
}
if (byte_queue_error(&eng->output)) {
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
return;
}
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
} else {
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
}
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
complete_message_body(eng);
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) {
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
byte_queue_write(&eng->output,
"HTTP/1.1 500 Internal Server Error\r\n"
"Content-Length: 0\r\n"
"Connection: Close\r\n"
"\r\n",
-1
);
}
if (byte_queue_error(&eng->output)) {
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
return;
}
byte_queue_read_ack(&eng->input, eng->reqsize);
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
}
}
void http_engine_undo(HTTP_Engine *eng)
{
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
return;
byte_queue_write_ack(&eng->output, 0);
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL;
else
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS;
}
-122
View File
@@ -1,122 +0,0 @@
#ifndef HTTP_ENGINE_INCLUDED
#define HTTP_ENGINE_INCLUDED
#ifndef HTTP_AMALGAMATION
#include "parse.h"
#endif
typedef enum {
HTTP_MEMFUNC_MALLOC,
HTTP_MEMFUNC_FREE,
} HTTP_MemoryFuncTag;
typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag,
void *ptr, int len, void *data);
typedef struct {
HTTP_MemoryFunc memfunc;
void *memfuncdata;
unsigned long long curs;
char* data;
unsigned int head;
unsigned int size;
unsigned int used;
unsigned int limit;
char* read_target;
unsigned int read_target_size;
int flags;
} HTTP_ByteQueue;
typedef unsigned long long HTTP_ByteQueueOffset;
#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0)
#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1)
#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2)
#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3)
#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4)
#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5)
#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6)
#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7)
#define HTTP_ENGINE_STATEBIT_PREP (1 << 8)
#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9)
#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10)
#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11)
#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12)
#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13)
#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14)
#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15)
typedef enum {
HTTP_ENGINE_STATE_NONE = 0,
HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL,
HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF,
HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK,
HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF,
HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK,
HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF,
HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK,
HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE,
HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED,
HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF,
HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK,
HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS,
HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF,
HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK,
HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF,
HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK,
HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT,
} HTTP_EngineState;
typedef struct {
HTTP_EngineState state;
HTTP_ByteQueue input;
HTTP_ByteQueue output;
int numexch;
int reqsize;
int closing;
int keepalive;
HTTP_ByteQueueOffset response_offset;
HTTP_ByteQueueOffset content_length_offset;
HTTP_ByteQueueOffset content_length_value_offset;
union {
HTTP_Request req;
HTTP_Response res;
} result;
} HTTP_Engine;
void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata);
void http_engine_free (HTTP_Engine *eng);
void http_engine_close (HTTP_Engine *eng);
HTTP_EngineState http_engine_state (HTTP_Engine *eng);
const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove
char* http_engine_recvbuf (HTTP_Engine *eng, int *cap);
void http_engine_recvack (HTTP_Engine *eng, int num);
char* http_engine_sendbuf (HTTP_Engine *eng, int *len);
void http_engine_sendack (HTTP_Engine *eng, int num);
HTTP_Request* http_engine_getreq (HTTP_Engine *eng);
HTTP_Response* http_engine_getres (HTTP_Engine *eng);
void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor);
void http_engine_status (HTTP_Engine *eng, int status);
void http_engine_header (HTTP_Engine *eng, HTTP_String str);
void http_engine_body (HTTP_Engine *eng, HTTP_String str);
void http_engine_bodycap (HTTP_Engine *eng, int mincap);
char* http_engine_bodybuf (HTTP_Engine *eng, int *cap);
void http_engine_bodyack (HTTP_Engine *eng, int num);
void http_engine_done (HTTP_Engine *eng);
void http_engine_undo (HTTP_Engine *eng);
#endif // HTTP_ENGINE_INCLUDED
+13
View File
@@ -0,0 +1,13 @@
#include <stdint.h>
#include <assert.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#else
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#endif
+7 -20
View File
@@ -1,16 +1,3 @@
#include <stdio.h> // snprintf
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <limits.h>
#ifndef HTTP_AMALGAMATION
#include "parse.h"
#include "basic.h"
#endif
// From RFC 9112
// request-target = origin-form
// / absolute-form
@@ -514,7 +501,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
s->cur - start,
};
if (s->cur == s->len || s->src[s->cur] != ':')
if (s->cur == s->len || s->src[s->cur] != ':')
return -1; // ERROR: Missing ':' after scheme
s->cur++;
}
@@ -577,7 +564,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port)
{
int ret;
ret = parse_host(s, host);
if (ret < 0) return ret;
@@ -658,7 +645,7 @@ static int parse_request_target(Scanner *s, HTTP_URL *url)
bool consume_str(Scanner *scan, HTTP_String token)
{
HTTP_ASSERT(token.len > 0);
assert(token.len > 0);
if (token.len > scan->len - scan->cur)
return false;
@@ -692,7 +679,7 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
// obs-text = %x80-FF
int start;
if (s->cur == s->len || !is_tchar(s->src[s->cur]))
return -1; // ERROR
start = s->cur;
@@ -740,7 +727,7 @@ parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
int num = 0;
for (;;) {
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
TransferEncodingOption opt;
@@ -1304,12 +1291,12 @@ int http_get_param_i(HTTP_String body, HTTP_String str)
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
{
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected
assert(idx != -1); // Requests without the host header are always rejected
char tmp[1<<8];
if (port > -1 && port != 80) {
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
HTTP_ASSERT(ret > 0);
assert(ret > 0);
domain = (HTTP_String) { tmp, ret };
}
-8
View File
@@ -1,9 +1,3 @@
#ifndef PARSE_INCLUDED
#define PARSE_INCLUDED
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#endif
#define HTTP_MAX_HEADERS 32
@@ -99,5 +93,3 @@ int http_get_param_i (HTTP_String body, HTTP_String str);
// Checks whether the request was meant for the host with the given
// domain an port. If port is -1, the default value of 80 is assumed.
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
#endif // PARSE_INCLUDED
-220
View File
@@ -1,220 +0,0 @@
#ifndef HTTP_AMALGAMATION
#include "sec.h"
#endif
#ifndef HTTPS_ENABLED
void secure_context_global_init(void)
{
}
void secure_context_global_free(void)
{
}
int secure_context_init_as_client(SecureContext *sec)
{
(void) sec;
return 0;
}
int secure_context_init_as_server(SecureContext *sec,
HTTP_String cert_file, HTTP_String key_file)
{
(void) sec;
(void) cert_file;
(void) key_file;
return 0;
}
int secure_context_add_cert(SecureContext *sec,
HTTP_String domain, HTTP_String cert_file,
HTTP_String key_file)
{
(void) sec;
(void) domain;
(void) cert_file;
(void) key_file;
return -1;
}
void secure_context_free(SecureContext *sec)
{
(void) sec;
}
#else
void secure_context_global_init(void)
{
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
void secure_context_global_free(void)
{
EVP_cleanup();
}
int secure_context_init_as_client(SecureContext *sec)
{
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (!ctx)
return -1;
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
SSL_CTX_free(ctx);
return -1;
}
sec->is_server = false;
sec->ctx = ctx;
sec->num_certs = 0;
return 0;
}
static int servername_callback(SSL *ssl, int *ad, void *arg)
{
SecureContext *sec = arg;
(void) ad; // TODO: use this?
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername == NULL)
return SSL_TLSEXT_ERR_NOACK;
for (int i = 0; i < sec->num_certs; i++) {
CertData *cert = &sec->certs[i];
if (!strcmp(cert->domain, servername)) {
SSL_set_SSL_CTX(ssl, cert->ctx);
return SSL_TLSEXT_ERR_OK;
}
}
return SSL_TLSEXT_ERR_NOACK;
}
int secure_context_init_as_server(SecureContext *sec,
HTTP_String cert_file, HTTP_String key_file)
{
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx)
return -1;
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
char cert_buffer[1024];
if (cert_file.len >= (int) sizeof(cert_buffer)) {
SSL_CTX_free(ctx);
return -1;
}
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
cert_buffer[cert_file.len] = '\0';
// Copy private key file path to static buffer
char key_buffer[1024];
if (key_file.len >= (int) sizeof(key_buffer)) {
SSL_CTX_free(ctx);
return -1;
}
memcpy(key_buffer, key_file.ptr, key_file.len);
key_buffer[key_file.len] = '\0';
// Load certificate and private key
if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
return -1;
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
return -1;
}
// Verify that the private key matches the certificate
if (SSL_CTX_check_private_key(ctx) != 1) {
SSL_CTX_free(ctx);
return -1;
}
SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback);
SSL_CTX_set_tlsext_servername_arg(ctx, sec);
sec->is_server = true;
sec->ctx = ctx;
sec->num_certs = 0;
return 0;
}
void secure_context_free(SecureContext *sec)
{
SSL_CTX_free(sec->ctx);
for (int i = 0; i < sec->num_certs; i++)
SSL_CTX_free(sec->certs[i].ctx);
}
int secure_context_add_cert(SecureContext *sec,
HTTP_String domain, HTTP_String cert_file,
HTTP_String key_file)
{
if (!sec->is_server)
return -1;
if (sec->num_certs == MAX_CERTS)
return -1;
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx)
return -1;
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
char cert_buffer[1024];
if (cert_file.len >= (int) sizeof(cert_buffer)) {
SSL_CTX_free(ctx);
return -1;
}
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
cert_buffer[cert_file.len] = '\0';
char key_buffer[1024];
if (key_file.len >= (int) sizeof(key_buffer)) {
SSL_CTX_free(ctx);
return -1;
}
memcpy(key_buffer, key_file.ptr, key_file.len);
key_buffer[key_file.len] = '\0';
if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
return -1;
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ctx);
return -1;
}
if (SSL_CTX_check_private_key(ctx) != 1) {
SSL_CTX_free(ctx);
return -1;
}
CertData *cert = &sec->certs[sec->num_certs];
if (domain.len >= (int) sizeof(cert->domain)) {
SSL_CTX_free(ctx);
return -1;
}
memcpy(cert->domain, domain.ptr, domain.len);
cert->domain[domain.len] = '\0';
cert->ctx = ctx;
sec->num_certs++;
return 0;
}
#endif
-55
View File
@@ -1,55 +0,0 @@
#ifndef SEC_INCLUDED
#define SEC_INCLUDED
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#endif
#ifndef HTTPS_ENABLED
typedef struct {
} SecureContext;
#else
#define MAX_CERTS 10
#include <stdbool.h>
#include <openssl/ssl.h>
typedef struct {
char domain[128];
SSL_CTX *ctx;
} CertData;
typedef struct {
bool is_server;
SSL_CTX *ctx;
// Only used when server
int num_certs;
CertData certs[MAX_CERTS];
} SecureContext;
#endif
void secure_context_global_init(void);
void secure_context_global_free(void);
int secure_context_init_as_client(SecureContext *sec);
int secure_context_init_as_server(SecureContext *sec,
HTTP_String cert_file, HTTP_String key_file);
int secure_context_add_cert(SecureContext *sec,
HTTP_String domain, HTTP_String cert_file,
HTTP_String key_file);
void secure_context_free(SecureContext *sec);
#endif // SEC_INCLUDED
+36
View File
@@ -0,0 +1,36 @@
int global_secure_context_init(void)
{
// TODO
}
int global_secure_context_free(void)
{
// TODO
}
int client_secure_context_init(ClientSecureContext *ctx)
{
// TODO
}
int client_secure_context_free(ClientSecureContext *ctx)
{
// TODO
}
int server_secure_context_init(ServerSecureContext *ctx)
{
// TODO
}
int server_secure_context_free(ServerSecureContext *ctx)
{
// TODO
}
int server_secure_context_add_certificate(ServerSecureContext *ctx,
String domain, String cert_file)
{
// TODO
}
+29
View File
@@ -0,0 +1,29 @@
int global_secure_context_init(void);
int global_secure_context_free(void);
typedef struct {
#ifdef HTTPS_ENABLED
// TODO
SSL_CTX *p;
#endif
} ClientSecureContext;
int client_secure_context_init(ClientSecureContext *ctx);
int client_secure_context_free(ClientSecureContext *ctx);
typedef struct {
} SecureDomain;
typedef struct {
#ifdef HTTPS_ENABLED
// TODO
SSL_CTX *p;
#endif
} ServerSecureContext;
int server_secure_context_init(ServerSecureContext *ctx);
int server_secure_context_free(ServerSecureContext *ctx);
int server_secure_context_add_certificate(ServerSecureContext *ctx,
String domain, String cert_file, String key_file);
+271 -253
View File
@@ -1,307 +1,325 @@
#include <stdint.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>
#ifndef HTTP_AMALGAMATION
#include "engine.h"
#include "server.h"
#include "socket_pool.h"
#endif
#define MAX_CONNS (1<<10)
typedef struct {
bool used;
uint16_t gen;
HTTP_Engine engine;
SocketHandle sock;
} Connection;
struct HTTP_Server {
SocketPool *socket_pool;
int num_conns;
Connection conns[MAX_CONNS];
int ready_head;
int ready_count;
int ready[MAX_CONNS];
};
HTTP_Server *http_server_init(HTTP_String addr, uint16_t port)
int http_server_init(HTTP_Server *server)
{
return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR(""));
}
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file)
{
HTTP_Server *server = malloc(sizeof(HTTP_Server));
if (server == NULL)
return NULL;
int backlog = 32;
bool reuse_addr = true;
SocketPool *socket_pool = socket_pool_init(addr, port, secure_port, MAX_CONNS, reuse_addr, backlog, cert_file, key_file);
if (socket_pool == NULL) {
free(server);
return NULL;
}
server->socket_pool = socket_pool;
server->num_conns = 0;
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++)
server->conns[i].state = HTTP_SERVER_CONN_FREE;
server->num_ready = 0;
server->ready_head = 0;
server->ready_count = 0;
for (int i = 0; i < MAX_CONNS; i++) {
server->conns[i].used = false;
server->conns[i].gen = 1;
}
return server;
if (socket_manager_init(&server->sockets,
&server->socket_pool, HTTP_SERVER_CAPACITY) < 0)
return -1;
return 0;
}
void http_server_free(HTTP_Server *server)
{
for (int i = 0, j = 0; j < server->num_conns; i++) {
socket_manager_free(&server->sockets);
if (!server->conns[i].used)
for (int i = 0, j = 0; j < server->num_conns; i++) {
HTTP_ServerConn *conn = &server->conns[i];
if (conn->state != HTTP_SERVER_CONN_FREE)
continue;
j++;
// TODO
http_server_conn_free(conn);
}
socket_pool_free(server->socket_pool);
free(server);
}
int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
int http_server_listen_tcp(HTTP_Server *server,
String addr, Port port)
{
return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file);
}
static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
(void)data;
switch (tag) {
case HTTP_MEMFUNC_MALLOC:
return malloc(len);
case HTTP_MEMFUNC_FREE:
free(ptr);
return NULL;
}
return NULL;
}
int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *builder)
{
while (server->ready_count == 0) {
SocketEvent event = socket_pool_wait(server->socket_pool);
switch (event.type) {
case SOCKET_EVENT_DIED:
{
Connection *conn = event.user_data;
if (conn) {
http_engine_free(&conn->engine);
conn->used = false;
conn->gen++;
server->num_conns--;
}
}
break;
case SOCKET_EVENT_READY:
{
Connection *conn = event.user_data;
if (conn == NULL) {
// Connection was just accepted
if (server->num_conns == MAX_CONNS) {
socket_pool_close(server->socket_pool, event.handle);
break;
}
int i = 0;
while (server->conns[i].used)
i++;
conn = &server->conns[i];
conn->used = true;
conn->sock = event.handle;
http_engine_init(&conn->engine, 0, server_memfunc, NULL);
socket_pool_set_user_data(server->socket_pool, event.handle, conn);
server->num_conns++;
}
switch (http_engine_state(&conn->engine)) {
int len;
char *buf;
case HTTP_ENGINE_STATE_SERVER_RECV_BUF:
buf = http_engine_recvbuf(&conn->engine, &len);
if (buf) {
int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len);
http_engine_recvack(&conn->engine, ret);
}
break;
case HTTP_ENGINE_STATE_SERVER_SEND_BUF:
buf = http_engine_sendbuf(&conn->engine, &len);
if (buf) {
int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len);
http_engine_sendack(&conn->engine, ret);
}
break;
default:
break;
}
switch (http_engine_state(&conn->engine)) {
int tail;
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS:
tail = (server->ready_head + server->ready_count) % MAX_CONNS;
server->ready[tail] = conn - server->conns;
server->ready_count++;
break;
case HTTP_ENGINE_STATE_SERVER_CLOSED:
socket_pool_close(server->socket_pool, conn->sock);
break;
default:
break;
}
}
break;
case SOCKET_EVENT_ERROR:
return -1;
case SOCKET_EVENT_SIGNAL:
return 1;
}
}
int index = server->ready[server->ready_head];
server->ready_head = (server->ready_head + 1) % MAX_CONNS;
server->ready_count--;
*req = http_engine_getreq(&server->conns[index].engine);
(*req)->secure = socket_pool_secure(server->socket_pool, server->conns[index].sock);
*builder = (HTTP_ResponseBuilder) { server, index, server->conns[index].gen };
if (socket_manager_listen_tcp(&server->sockets, addr, port) < 0)
return -1;
return 0;
}
static Connection*
server_builder_to_conn(HTTP_ResponseBuilder builder)
int http_server_listen_tls(HTTP_Server *server,
String addr, Port port, String cert_file_name,
String key_file_name)
{
HTTP_Server *server = builder.data0;
if (builder.data1 >= MAX_CONNS)
return NULL;
Connection *conn = &server->conns[builder.data1];
if (conn->gen != builder.data2)
return NULL;
return conn;
if (socket_manager_listen_tls(&server->sockets, addr,
port, cert_file_name, key_file_name) < 0)
return -1;
return 0;
}
void http_response_builder_status(HTTP_ResponseBuilder res, int status)
int http_server_add_certificate(HTTP_Server *server,
String domain, String cert_file, String key_file)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL)
return;
http_engine_status(&conn->engine, status);
if (socket_manager_add_certificate(&server->sockets,
domain, cert_file, key_file) < 0)
return -1;
return 0;
}
void http_response_builder_header(HTTP_ResponseBuilder res, HTTP_String str)
int http_server_wakeup(HTTP_Server *server)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL)
return;
http_engine_header(&conn->engine, str);
if (socket_manager_wakeup(&server->sockets) < 0)
return -1;
return 0;
}
void http_response_builder_body(HTTP_ResponseBuilder res, HTTP_String str)
int http_server_register_events(HTTP_Server *server,
struct pollfd *polled, int max_polled)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL)
return;
http_engine_body(&conn->engine, str);
return socket_manager_register_events(&server->sockets, polled, max_polled);
}
void http_response_builder_bodycap(HTTP_ResponseBuilder res, int mincap)
// Look at the head of the input buffer to see if
// a request was buffered. If it was, change the
// connection's status to WAIT_STATUS and push it
// to the ready queue. If the request is invalid,
// close the socket.
static void
check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL)
return;
assert(conn->state == HTTP_SERVER_CONN_BUFFERING);
http_engine_bodycap(&conn->engine, mincap);
ByteView src = byte_queue_read_buf(&conn->input);
int ret = http_parse_request(src.ptr, src.len, &conn->request);
if (ret < 0) {
// Invalid request
byte_queue_read_ack(&conn->input, 0);
socket_close(&server->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 request, abort the exchange.
if (byte_queue_full(&conn->input))
socket_close(&server->sockets, conn->handle);
} else {
// Ready
assert(ret == 1);
conn->state = HTTP_SERVER_CONN_STATUS;
conn->request_len = ret;
conn->response_offset = byte_queue_offset(&conn->output);
// Push to the ready queue
assert(server->num_ready < HTTP_SERVER_CAPACITY);
int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY;
server->ready[tail] = conn - server->conns;
server->num_ready++;
}
}
char *http_response_builder_bodybuf(HTTP_ResponseBuilder res, int *cap)
bool http_server_next_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL) {
*cap = 0;
return NULL;
}
if (server->num_ready == 0)
return false;
return http_engine_bodybuf(&conn->engine, cap);
HTTP_ServerConn *conn = &server->conns[server->ready_head];
server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY;
server->num_ready--;
assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS);
*request = &conn->request;
*builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
return true;
}
void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num)
int http_server_process_events(HTTP_Server *server,
struct pollfd *polled, int num_polled)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL)
return;
SocketEvent events[HTTP_SERVER_CAPACITY];
int num_events = socket_manger_translate_events(&server->sockets, polled, num_polled);
if (num_events < 0)
return -1;
http_engine_bodyack(&conn->engine, num);
for (int i = 0; i < num_events; i++) {
HTTP_ServerConn *conn = events[i].user;
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
http_server_conn_free(conn);
server->num_conns--;
} else if (events[i].type == SOCKET_EVENT_READY) {
if (events[i].user == NULL) {
if (server->num_conns == HTTP_SERVER_CAPACITY) {
socket_close(&server->sockets, events[i].handle);
continue;
}
int i = 0;
while (server->conns[i].state != HTTP_SERVER_CONN_FREE) {
i++;
assert(i < HTTP_SERVER_CAPACITY);
}
conn = &server->conns[i];
http_server_conn_init(conn, events[i].handle);
server->num_conns++;
socket_set_user(&server->sockets, events[i].handle, conn);
}
if (conn->state == HTTP_SERVER_CONN_BUFFERING) {
int min_recv = 1<<10;
byte_queue_write_setmincap(&conn->input, min_recv);
// Note that it's extra important that we don't
// buffer while the user is building the response.
// If we did that, a resize would invalidate all
// pointers on the parsed request structure.
int num = 0;
ByteView dst = byte_queue_write_buf(&conn->input);
if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len);
byte_queue_write_ack(&conn->input, num);
if (byte_queue_error(&conn->output))
socket_close(&server->sockets, conn->handle);
else
check_request_buffer(server, conn);
} else if (conn->state == HTTP_SERVER_CONN_FLUSHING) {
int num = 0;
ByteView src = byte_queue_read_buf(&conn->output);
if (src.len) num = socket_recv(&server->sockets, conn->handle, src.ptr, src.len);
byte_queue_read_ack(&conn->output, num);
if (byte_queue_error(&conn->output))
socket_close(&server->sockets, conn->handle);
else if (byte_queue_empty(&conn->output)) {
// We finished sending the response. Now we can
// either close the connection or process a new
// buffered request.
if (conn->closing) {
socket_close(&server->sockets, conn->handle);
} else {
check_request_buffer(server, conn);
}
}
}
}
}
return 0;
}
void http_response_builder_undo(HTTP_ResponseBuilder res)
// Get a connection pointer from a response builder.
// If the builder is invalid, returns NULL.
// Note that only connections in the responding states
// can be returned, as any builder is invalidated by
// incrementing the connection's generation counter
// when a response is completed.
static HTTP_ServerConn*
builder_to_conn(HTTP_ResponseBuilder builder)
{
Connection *conn = server_builder_to_conn(res);
if (conn == NULL)
return;
HTTP_Server *server = builder.server;
if (server == NULL)
return NULL;
http_engine_undo(&conn->engine);
if (server->index > HTTP_SERVER_CAPACITY)
return NULL;
HTTP_ServerConn *conn = server->conns[server->index];
if (conn->gen != builder.gen)
return NULL;
return conn;
}
void http_response_builder_done(HTTP_ResponseBuilder res)
static void
write_status(HTTP_ServerConn *conn, int status)
{
HTTP_Server *server = res.data0;
Connection *conn = server_builder_to_conn(res);
byte_queue_write(&conn->output, xxx);
}
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
http_engine_done(&conn->engine);
conn->gen++;
if (conn->gen == 0 || conn->gen == UINT16_MAX)
conn->gen = 1;
HTTP_EngineState state = http_engine_state(&conn->engine);
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
int tail = (server->ready_head + server->ready_count) % MAX_CONNS;
server->ready[tail] = res.data1;
server->ready_count++;
if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) {
// Reset all response content and start from scrach.
byte_queue_remove_from_offset(&conn->output, conn->response_offset);
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
}
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
socket_pool_close(server->socket_pool, conn->sock);
}
write_status(conn, status);
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
}
void http_response_builder_header(HTTP_ResponseBuilder builder, String str)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
return;
byte_queue_write(&conn->output, xxx);
}
static void append_special_headers(HTTP_ServerConn *conn)
{
// TODO
}
static void patch_special_headers(HTTP_ServerConn *conn)
{
// TODO
}
void http_response_builder_body(HTTP_ResponseBuilder builder, String str)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
}
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
return;
byte_queue_write(&conn->output, str);
}
void http_response_builder_send(HTTP_ResponseBuilder builder, String str)
{
HTTP_ServerConn *conn = builder_to_conn(builder);
if (conn == NULL)
return;
if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) {
write_status(conn, 500);
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
}
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
append_special_headers(conn);
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
}
assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY);
patch_special_headers(conn);
// Remove the buffered request
byte_queue_read_ack(&conn->input, conn->request_len);
conn->state = HTTP_SERVER_CONN_FLUSHING;
conn->gen++;
}
+164 -25
View File
@@ -1,35 +1,174 @@
#ifndef HTTP_SERVER_INCLUDED
#define HTTP_SERVER_INCLUDED
#include <stdint.h>
#ifndef HTTP_AMALGAMATION
#include "parse.h"
#ifndef HTTP_SERVER_CAPACITY
// The maximum ammount of requests that can be handled
// in parallel.
#define HTTP_SERVER_CAPACITY (1<<9)
#endif
typedef enum {
// This struct is unused
HTTP_SERVER_CONN_FREE,
// No request was buffered yet.
HTTP_SERVER_CONN_BUFFERING,
// A request was just buffered and is waiting for
// the user to build a response. To be specific,
// it's waiting for the user to set a response status.
HTTP_SERVER_CONN_WAIT_STATUS,
// A request is buffered and a status was set. Now
// the user can set a header or append the first
// bytes of the response body.
HTTP_SERVER_CONN_WAIT_HEADER,
// A request is buffered and some bytes were appended
// to the response. Now the user can either append more
// bytes or send out the response.
HTTP_SERVER_CONN_WAIT_BODY,
// A response has been produced and it's being flushed.
HTTP_SERVER_CONN_FLUSHING,
} HTTP_ServerConnState;
// This structure represents the HTTP connection to
// a client.
typedef struct {
void *data0;
int data1;
int data2;
// If false, this struct is unused
HTTP_ServerConnState state;
// Handle to the socket
SocketHandle handle;
// Data received by the client
ByteQueue input;
// Data being sent to the client
ByteQueue output;
// When the state is WAIT_STATUS, WAIT_HEADER,
// or WAIT_BODY, this contains the parsed version
// of the buffered request.
HTTP_Request request;
// Length of the buffered request when the request
// field is valid.
int request_len;
// Offset of the first response byte in the output
// buffer. This is useful when the user wants to
// undo the response it's building and start from
// scratch.
ByteQueueOffset response_offset;
} HTTP_ServerConn;
typedef struct {
// Array of connections. The counter contains the
// number of structs such that state=FREE.
int num_conns;
HTTP_ServerConn conns[HTTP_SERVER_CAPACITY];
// Queue of indices referring to connections that
// are in the WAIT_STATUS state.
int num_ready;
int ready_head;
int ready[HTTP_SERVER_CAPACITY];
// Asynchronous TCP and TLS socket abstraction
SocketManager sockets;
// The server 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_SERVER_CAPACITY];
} HTTP_Server;
// Initialize the HTTP server object. By default, it won't
// listen for connections. You need to call
//
// http_server_listen_tcp
// http_server_listen_tls
//
// to listen for connection. Note that you can have a
// single server listening for HTTP and HTTPS requests
// by calling both.
int http_server_init(HTTP_Server *server);
// Release resources associated to the server.
void http_server_free(HTTP_Server *server);
// Enable listening for plain HTTP requests at the
// specified interface.
int http_server_listen_tcp(HTTP_Server *server,
String addr, Port port);
// Enable listening for HTTPS requests at the specified
// interfact, using the specified certificate and key
// to verify the connection.
int http_server_listen_tls(HTTP_Server *server,
String addr, Port port, String cert_file_name,
String key_file_name);
// Add the certificate for an additional domain when
// the server is listening for HTTPS requests.
int http_server_add_certificate(HTTP_Server *server,
String domain, String cert_file, String key_file);
// When a thread is blocked waiting for server events,
// other threads can call this function to wake it up.
int http_server_wakeup(HTTP_Server *server);
// List all low-level socket events the server is
// waiting for such that the caller can call poll()
// with it.
int http_server_register_events(HTTP_Server *server,
struct pollfd *polled, int max_polled);
// The caller has waited for poll() to return and some
// I/O events to be triggered, so now the HTTP server
// can continue its buffering and flushing operations.
int http_server_process_events(HTTP_Server *server,
struct pollfd *polled, int num_polled);
typedef struct {
HTTP_Server *server;
uint16_t index;
uint16_t gen;
} HTTP_ResponseBuilder;
typedef struct HTTP_Server HTTP_Server;
// After some I/O events were processes, some requests
// may be availabe. This function returns one of the
// buffered requests. If a request was available, true
// is returned. If no more are avaiable, false is returned.
// Note that It's possible to get multiple requests to
// respond in batches.
// For each request returned by this function, the user
// must build a response using the response builder API.
bool http_server_next_request(HTTP_Server *server,
HTTP_Request **request, HTTP_ResponseBuilder *builder);
HTTP_Server *http_server_init(HTTP_String addr, uint16_t port);
// This function is called to set the status code of
// a request's response. If this function is called
// after the other response builder functions, it will
// reset the response and set a new status.
void http_response_builder_status(HTTP_ResponseBuilder builder, int status);
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key);
// Append a header to the response. This can only be
// used after having set the status and before appending
// to the body.
void http_response_builder_header(HTTP_ResponseBuilder builder, String str);
void http_server_free (HTTP_Server *server);
int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *handle);
int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
void http_response_builder_status (HTTP_ResponseBuilder res, int status);
void http_response_builder_header (HTTP_ResponseBuilder res, HTTP_String str);
void http_response_builder_body (HTTP_ResponseBuilder res, HTTP_String str);
void http_response_builder_bodycap (HTTP_ResponseBuilder res, int mincap);
char* http_response_builder_bodybuf (HTTP_ResponseBuilder res, int *cap);
void http_response_builder_bodyack (HTTP_ResponseBuilder res, int num);
void http_response_builder_undo (HTTP_ResponseBuilder res);
void http_response_builder_done (HTTP_ResponseBuilder res);
// Append some bytes to the response's body
void http_response_builder_body(HTTP_ResponseBuilder builder, String str);
#endif // HTTP_SERVER_INCLUDED
// Mark the response as complete. This will invalidate
// the response builder handle.
void http_response_builder_send(HTTP_ResponseBuilder builder, String str);
+777 -644
View File
File diff suppressed because it is too large Load Diff
+282 -55
View File
@@ -1,75 +1,302 @@
#ifndef SOCKET_INCLUDED
#define SOCKET_INCLUDED
// This file (and its relative .c file) implements an asynchronous TCP/TLS
// server and client abstraction.
//
// It introduces the concept of a "socket manager", which is a pool of
// connection sockets and a listener socket. The listener is managed
// internally, which means the manager automatically accepts sockets
// from it and adds them to the pool.
//
// If the listener is configured using the function:
//
// socket_manager_listen_tcp
//
// the resulting connections will not use TLS. If instead the listener
// is configured using:
//
// socket_manager_listen_tls
//
// the listener will use TLS. Note that both functions can be used on
// the same manager to allow both plaintext and encrypted connections.
// Users may enable zero listeners, in which case only outgoing
// connections are allowed (more on this later).
//
// Once the manager is set up, one can wait for events by following
// this pattern:
//
// struct pollfd polled[...];
// int num_polled = socket_manager_register_events(sm, polled, max_polled);
// poll(polled, num_polled, -1);
//
// #define MAX_EVENTS ...
// SocketEvent events[MAX_EVENTS];
// int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled);
// for (int i = 0; i < num_events; i++) {
// ... Here call socket_recv, socket_send, socket_close, ...
// }
//
// Note that from the user's perspective, there is no difference
// between connections that use plain TCP and those that use TCP/TLS.
//
// Users can also establish outgoing connections by calling the
// function:
//
// socket_connect
//
// Which allows the creation of a connection towards an host given
// its domain, IPv4, IPv6, or an array of them. This can be done both
// for TCP and TCP/TLS connection. Note that users that only intend
// to establish outgoing connection may omit the configuration of
// listeners entirely.
// This module implements the socket state machine to encapsulate
// the complexity of non-blocking TCP and TLS sockets.
//
// A socket is represented by the "Socket" structure, which may
// be in a number of states. As far as an user of the interface
// is concerned, the socket may be DIED, READY, or in an internal
// state that requires waiting for an event. Therefore, if the
// socket is not DIED or READY, the user needs to wait for the
// events specified in the [socket->events] field, then call the
// socket_update function. At some point the socket will become
// either READY or DIED.
//
// When the socket reaches the DIED state, the user must call
// socket_free.
//
// If the socket is ESTABLISHED_READY, the user may call socket_read,
// socket_write, or socket_close on it.
#ifndef HTTP_AMALGAMATION
#include "sec.h"
#include "parse.h"
#include "socket_raw.h"
#ifdef _WIN32
#define NATIVE_SOCKET SOCKET
#define NATIVE_SOCKET_INVALID SOCKET_ERROR
#define CLOSE_NATIVE_SOCKET closesocket
#else
#define NATIVE_SOCKET int
#define NATIVE_SOCKET_INVALID -1
#define CLOSE_NATIVE_SOCKET close
#endif
typedef struct PendingConnect PendingConnect;
typedef uint32_t SocketHandle;
#define SOCKET_HANDLE_INVALID ((SocketHandle) 0)
typedef struct {
char *ptr;
int len;
} String;
typedef uint16_t Port;
typedef struct {
uint32_t data;
} IPv4;
typedef struct {
uint16_t data[8];
} IPv6;
// These should only be relevant to socket.c
typedef enum {
SOCKET_EVENT_READY,
SOCKET_EVENT_DISCONNECT,
} SocketEventType;
typedef struct {
SocketEventType type;
SocketHandle handle;
void* user;
} SocketEvent;
// Internal use only
typedef enum {
// The Socket struct is unused
SOCKET_STATE_FREE,
SOCKET_STATE_DIED,
SOCKET_STATE_ESTABLISHED_WAIT,
SOCKET_STATE_ESTABLISHED_READY,
// The state associated to a socket created
// by a connect operation that hasn't been
// processed yet.
SOCKET_STATE_PENDING,
SOCKET_STATE_ACCEPTED,
SOCKET_STATE_CONNECTED,
// A connect() operation was started but is
// still pending.
SOCKET_STATE_CONNECTING,
// The connection was estabished, but a TLS
// handshake may need to be performed.
SOCKET_STATE_CONNECTED,
// The connection was esablished, but the user
// wants to perform a read or write operation that
// would block.
SOCKET_STATE_ESTABLISHED_WAIT,
// The connection was established and it's possible
// to perform read or write operations on it without
// blocking.
SOCKET_STATE_ESTABLISHED_READY,
// The socket was marked to be closed.
SOCKET_STATE_SHUTDOWN,
// The current socket is was closed. The only
// valid thing to do here is free its resources.
SOCKET_STATE_DIED,
} SocketState;
// Internal use only
typedef struct {
union {
IPv4 ipv4;
IPv6 ipv6;
};
bool is_ipv4;
Port port;
} AddressAndPort;
// Internal use only
typedef struct {
SocketState state;
RAW_SOCKET raw;
// OS-specific socket type
NATIVE_SOCKET sock;
// Native socket events that need to be monitored
int events;
void *user_data;
PendingConnect *pending_connect;
#ifdef HTTPS_ENABLED
SSL *ssl;
#endif
SecureContext *sec;
// User-provided context pointer
void *user;
// A single connect operation may involve
// trying to establish a connection towards
// one of a set of addresses.
int num_addr;
int next_addr;
union {
AddressAndPort addr; // When num_addr=1
AddressAndPort *addrs; // Dynamically allocated when num_addr>1
};
} Socket;
void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data);
void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data);
void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data);
void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw);
void socket_update(Socket *sock);
void socket_close(Socket *sock);
bool socket_ready(Socket *sock);
bool socket_died(Socket *sock);
int socket_read(Socket *sock, char *dst, int max);
int socket_write(Socket *sock, char *src, int len);
void socket_free(Socket *sock);
bool socket_secure(Socket *sock);
void socket_set_user_data(Socket *sock, void *user_data);
void* socket_get_user_data(Socket *sock);
// Glorified array of sockets. This structure
// is private to the .c file associated to this
// header.
typedef struct {
#endif // SOCKET_INCLUDED
// This guards access to the main thread using
// the manager from other threads calling the
// wakeup function.
Mutex mutex;
// TCP listener sockets. The first is intended
// for plaintext, while the second is for TLS.
// The socket manager will accept and add new
// sockets to the pool automatically. Note that
// either may be unset. If both are unset, users
// can only create outgoing connections.
NATIVE_SOCKET plain_sock;
NATIVE_SOCKET secure_sock;
// Handles for the self-pipe trick necessary for
// other threads to wake up sockets blocked on
// poll().
NATIVE_SOCKET wait_sock;
NATIVE_SOCKET signal_sock;
// TLS contexts. One is used for outgoing connections
// (the client context) and one for incoming
// connections (server). If the secure_sock is
// set, the server context is initialized. If at
// least one connect was performed using TLS
// (and the flag is set), the client context is
// initialized.
bool at_least_one_secure_connect;
ClientSecureContext client_secure_context;
ServerSecureContext server_secure_context;
// Array of sockets. Structs with state FREE
// are unused.
int num_used;
int max_used;
Socket *sockets;
} SocketManager;
// Instanciate a socket manager. Returns 0 on
// success and -1 on error.
int socket_manager_init(SocketManager *sm, Socket *socks,
int num_socks);
// Deinitialize a socket manager
void socket_manager_free(SocketManager *sm);
// Configure the socket manager to listen on
// the specified interface for TCP connections.
// Incoming connections will be automatically
// added to the internal pool. This function
// can only be used once per manager.
// Returns 0 on success, -1 on error.
int socket_manager_listen_tcp(SocketManager *sm,
String addr, Port port);
// Same as the previous function, but incoming
// connections will be interpreted as TLS. You
// can only call this function once per manager,
// but you can call this and the plaintext variant
// on the same manager to accept both plaintext
// and secure connections.
// Returns 0 on success, -1 on error.
int socket_manager_listen_tls(SocketManager *sm,
String addr, Port port, String cert_file_name,
String key_file_name);
// If the socket manager was configures to accept
// TLS connections, this adds additional certificates
// the client can use to verify the server's
// authenticity.
// Returns 0 on success, -1 on error.
int socket_manager_add_certificate(SocketManager *sm,
String domain, String cert_file, String key_file);
// When a thread is blocked on a poll() call for
// descriptors associated to this socket manager,
// other threads can call this function to wake
// up that blocked thread.
// Returns 0 on success, -1 on error.
int socket_manager_wakeup(SocketManager *sm);
// Writes to the polled array the list of sockets
// that the socket manager is monitoring and which
// events associated to that socket.
// Returns the number of items written to the polled
// array.
int socket_manager_register_events(SocketManager *sm,
struct pollfd *polled, int max_polled);
// After poll() is called on the previously registered
// pollfd array and the revents fields are set, this
// function processes those events to produce higher-level
// socket events. Returns the number of socket events
// written to the output array, or -1 on error.
int socket_manager_translate_events(SocketManager *sm,
SocketEvent *events, int max_events, struct pollfd *polled,
int num_polled);
typedef enum {
CONNECT_TARGET_NAME,
CONNECT_TARGET_IPV4,
CONNECT_TARGET_IPV6,
} ConnectTargetType;
typedef struct {
ConnectTargetType type;
Port port;
union {
IPv4 ipv4;
IPv6 ipv6;
String name;
};
} ConnectTarget;
// Connect to one of the given targets. The socket
// manager will try to connecting to addresses until
// one succedes. If secure=true, the socket uses TLS.
// Returns 0 on success, -1 on error.
int socket_connect(SocketManager *sm, int num_targets,
ConnectTarget *targets, bool secure, void *user);
int socket_recv(SocketManager *sm, SocketHandle handle,
char *dst, int max);
int socket_send(SocketManager *sm, SocketHandle handle,
char *src, int len);
void socket_close(SocketManager *sm, SocketHandle handle);
// Returns -1 on error, 0 if the socket was accepted
// from the plaintext listener, or 1 if it was accepted
// by the secure listener.
int socket_is_secure(SocketManager *sm, SocketHandle handle);
// Set the user pointer of a socket
void socket_set_user(SocketManager *sm, SocketHandle handle);
-357
View File
@@ -1,357 +0,0 @@
#include <assert.h>
#include <stdlib.h>
#ifdef __linux__
#include <errno.h>
#include <sys/socket.h>
#endif
#ifndef HTTP_AMALGAMATION
#include "socket_pool.h"
#endif
#define SOCKET_HARD_LIMIT (1<<10)
#define MAX_CERTS 10
struct SocketPool {
SecureContext sec;
RAW_SOCKET listen_sock;
RAW_SOCKET secure_sock;
int num_socks;
int max_socks;
Socket socks[];
};
int socket_pool_global_init(void)
{
int ret = socket_raw_global_init();
if (ret < 0)
return -1;
secure_context_global_init();
return 0;
}
void socket_pool_global_free(void)
{
secure_context_global_free();
socket_raw_global_free();
}
SocketPool *socket_pool_init(HTTP_String addr,
uint16_t port, uint16_t secure_port, int max_socks,
bool reuse_addr, int backlog, HTTP_String cert_file,
HTTP_String key_file)
{
if (max_socks > SOCKET_HARD_LIMIT)
return NULL;
SocketPool *pool = malloc(sizeof(SocketPool) + max_socks * sizeof(Socket));
if (pool == NULL)
return NULL;
pool->num_socks = 0;
pool->max_socks = max_socks;
for (int i = 0; i < pool->max_socks; i++)
pool->socks[i].state = SOCKET_STATE_FREE;
if (port == 0)
pool->listen_sock = BAD_SOCKET;
else {
pool->listen_sock = listen_socket(addr, port, reuse_addr, backlog);
if (pool->listen_sock == BAD_SOCKET) {
free(pool);
return NULL;
}
}
if (secure_port == 0)
pool->secure_sock = BAD_SOCKET;
else {
#ifndef HTTPS_ENABLED
(void) cert_file;
(void) key_file;
if (pool->listen_sock != BAD_SOCKET)
CLOSE_SOCKET(pool->listen_sock);
free(pool);
return NULL;
#else
if (secure_context_init_as_server(&pool->sec, cert_file, key_file) < 0) {
if (pool->listen_sock != BAD_SOCKET)
CLOSE_SOCKET(pool->listen_sock);
free(pool);
return NULL;
}
pool->secure_sock = listen_socket(addr, secure_port, reuse_addr, backlog);
if (pool->secure_sock == BAD_SOCKET) {
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
free(pool);
return NULL;
}
#endif
}
#ifdef HTTPS_ENABLED
if (port == 0 && secure_port == 0) {
if (secure_context_init_as_client(&pool->sec) < 0) {
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock);
free(pool);
return NULL;
}
}
#endif
for (int i = 0; i < max_socks; i++)
pool->socks[i].state = SOCKET_STATE_FREE;
return pool;
}
void socket_pool_free(SocketPool *pool)
{
for (int i = 0, j = 0; j < pool->num_socks; i++) {
Socket *sock = &pool->socks[i];
if (sock->state == SOCKET_STATE_FREE)
continue;
j++;
socket_free(sock);
}
secure_context_free(&pool->sec);
if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock);
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
}
int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
{
return secure_context_add_cert(&pool->sec, domain, cert_file, key_file);
}
void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data)
{
Socket *sock = &pool->socks[handle];
socket_set_user_data(sock, user_data);
}
void socket_pool_close(SocketPool *pool, SocketHandle handle)
{
Socket *sock = &pool->socks[handle];
socket_close(sock);
}
static Socket *find_free_socket(SocketPool *pool)
{
if (pool->num_socks == pool->max_socks)
return NULL;
int i = 0;
while (pool->socks[i].state != SOCKET_STATE_FREE)
i++;
return &pool->socks[i];
}
int socket_pool_connect(SocketPool *pool, bool secure,
HTTP_String addr, uint16_t port, void *user_data)
{
Socket *sock = find_free_socket(pool);
if (sock == NULL)
return -1;
socket_connect(sock, secure ? &pool->sec : NULL, addr, port, user_data);
if (socket_died(sock)) {
socket_free(sock);
return -1;
}
pool->num_socks++;
return 0;
}
int socket_pool_connect_ipv4(SocketPool *pool, bool secure,
HTTP_IPv4 addr, uint16_t port, void *user_data)
{
Socket *sock = find_free_socket(pool);
if (sock == NULL)
return -1;
socket_connect_ipv4(sock, secure ? &pool->sec : NULL, addr, port, user_data);
if (socket_died(sock)) {
socket_free(sock);
return -1;
}
pool->num_socks++;
return 0;
}
int socket_pool_connect_ipv6(SocketPool *pool, bool secure,
HTTP_IPv6 addr, uint16_t port, void *user_data)
{
Socket *sock = find_free_socket(pool);
if (sock == NULL)
return -1;
socket_connect_ipv6(sock, secure ? &pool->sec : NULL, addr, port, user_data);
if (socket_died(sock)) {
socket_free(sock);
return -1;
}
pool->num_socks++;
return 0;
}
#include <stdio.h> // TODO: remove
SocketEvent socket_pool_wait(SocketPool *pool)
{
for (;;) {
// First, iterate over all sockets to find one that
// died or is ready.
for (int i = 0, j = 0; j < pool->num_socks; i++) {
Socket *sock = &pool->socks[i];
if (sock->state == SOCKET_STATE_FREE)
continue;
j++;
if (socket_died(sock)) {
void *user_data = socket_get_user_data(sock);
socket_free(sock);
pool->num_socks--;
return (SocketEvent) { SOCKET_EVENT_DIED, -1, user_data };
}
if (socket_ready(sock))
return (SocketEvent) { SOCKET_EVENT_READY, i, socket_get_user_data(sock) };
assert(sock->events);
}
// If we reached this point, we either have no sockets
// or all sockets need to wait for some event. Waiting
// when no sockets are available is only allowed when
// the pool is in server mode.
int indices[SOCKET_HARD_LIMIT+2];
struct pollfd polled[SOCKET_HARD_LIMIT+2];
int num_polled = 0;
if (pool->num_socks < pool->max_socks) {
if (pool->listen_sock != BAD_SOCKET) {
indices[num_polled] = -1;
polled[num_polled].fd = pool->listen_sock;
polled[num_polled].events = POLLIN;
polled[num_polled].revents = 0;
num_polled++;
}
if (pool->secure_sock != BAD_SOCKET) {
indices[num_polled] = -1;
polled[num_polled].fd = pool->secure_sock;
polled[num_polled].events = POLLIN;
polled[num_polled].revents = 0;
num_polled++;
}
}
for (int i = 0, j = 0; j < pool->num_socks; i++) {
Socket *sock = &pool->socks[i];
if (sock->state == SOCKET_STATE_FREE)
continue;
j++;
indices[num_polled] = i;
polled[num_polled].fd = sock->raw;
polled[num_polled].events = sock->events;
polled[num_polled].revents = 0;
num_polled++;
}
if (num_polled == 0)
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
int ret = POLL(polled, num_polled, -1);
if (ret < 0) {
if (errno == EINTR)
return (SocketEvent) { SOCKET_EVENT_SIGNAL, -1, NULL };
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
}
for (int i = 0; i < num_polled; i++) {
Socket *sock;
if (polled[i].fd == pool->listen_sock || polled[i].fd == pool->secure_sock) {
bool secure = false;
if (polled[i].fd == pool->secure_sock)
secure = true;
Socket *sock = find_free_socket(pool);
if (sock == NULL)
continue;
RAW_SOCKET raw = accept(polled[i].fd, NULL, NULL);
if (raw == BAD_SOCKET)
continue;
socket_accept(sock, secure ? &pool->sec : NULL, raw);
if (socket_died(sock)) {
socket_free(sock);
continue;
}
pool->num_socks++;
} else {
int j = indices[i];
sock = &pool->socks[j];
if (polled[i].revents)
socket_update(sock);
}
}
}
// This branch is unreachable
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
}
int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len)
{
return socket_read(&pool->socks[handle], dst, len);
}
int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len)
{
return socket_write(&pool->socks[handle], src, len);
}
bool socket_pool_secure(SocketPool *pool, SocketHandle handle)
{
return socket_secure(&pool->socks[handle]);
}
-60
View File
@@ -1,60 +0,0 @@
#ifndef SOCKET_POOL_INCLUDED
#define SOCKET_POOL_INCLUDED
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#include "socket.h"
#include "socket_raw.h"
#endif
typedef struct SocketPool SocketPool;
typedef int SocketHandle;
typedef enum {
SOCKET_EVENT_DIED,
SOCKET_EVENT_READY,
SOCKET_EVENT_ERROR,
SOCKET_EVENT_SIGNAL,
} SocketEventType;
typedef struct {
SocketEventType type;
SocketHandle handle;
void *user_data;
} SocketEvent;
int socket_pool_global_init(void);
void socket_pool_global_free(void);
SocketPool *socket_pool_init(HTTP_String addr,
uint16_t port, uint16_t secure_port, int max_socks,
bool reuse_addr, int backlog, HTTP_String cert_file,
HTTP_String key_file);
void socket_pool_free(SocketPool *pool);
int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
SocketEvent socket_pool_wait(SocketPool *pool);
void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data);
void socket_pool_close(SocketPool *pool, SocketHandle handle);
int socket_pool_connect(SocketPool *pool, bool secure,
HTTP_String addr, uint16_t port, void *user_data);
int socket_pool_connect_ipv4(SocketPool *pool, bool secure,
HTTP_IPv4 addr, uint16_t port, void *user_data);
int socket_pool_connect_ipv6(SocketPool *pool, bool secure,
HTTP_IPv6 addr, uint16_t port, void *user_data);
int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len);
int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len);
bool socket_pool_secure(SocketPool *pool, SocketHandle handle);
#endif // SOCKET_POOL_INCLUDED
-106
View File
@@ -1,106 +0,0 @@
#include <string.h>
#ifdef _WIN32
#include <ws2tcpip.h>
#endif
#ifdef __linux__
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
#ifndef HTTP_AMALGAMATION
#include "socket_raw.h"
#endif
int socket_raw_global_init(void)
{
#ifdef _WIN32
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0)
return 1;
#endif
return 0;
}
void socket_raw_global_free(void)
{
#ifdef _WIN32
WSACleanup();
#endif
}
int set_socket_blocking(RAW_SOCKET sock, bool value)
{
#ifdef _WIN32
u_long mode = !value;
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
return -1;
#endif
#ifdef __linux__
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0)
return -1;
if (value) flags &= ~O_NONBLOCK;
else flags |= O_NONBLOCK;
if (fcntl(sock, F_SETFL, flags) < 0)
return -1;
#endif
return 0;
}
RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog)
{
RAW_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == BAD_SOCKET)
return BAD_SOCKET;
if (set_socket_blocking(sock, false) < 0) {
CLOSE_SOCKET(sock);
return BAD_SOCKET;
}
if (reuse_addr) {
int one = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one));
}
struct in_addr addr_buf;
if (addr.len == 0)
addr_buf.s_addr = htonl(INADDR_ANY);
else {
char copy[100];
if (addr.len >= (int) sizeof(copy)) {
CLOSE_SOCKET(sock);
return BAD_SOCKET;
}
memcpy(copy, addr.ptr, addr.len);
copy[addr.len] = '\0';
if (inet_pton(AF_INET, copy, &addr_buf) < 0) {
CLOSE_SOCKET(sock);
return BAD_SOCKET;
}
}
struct sockaddr_in bind_buf;
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?
CLOSE_SOCKET(sock);
return BAD_SOCKET;
}
if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows?
CLOSE_SOCKET(sock);
return BAD_SOCKET;
}
return sock;
}
-35
View File
@@ -1,35 +0,0 @@
#ifndef SOCKET_RAW_INCLUDED
#define SOCKET_RAW_INCLUDED
#include <stdint.h>
#include <stdbool.h>
#ifdef _WIN32
#include <winsock2.h>
#define RAW_SOCKET SOCKET
#define BAD_SOCKET INVALID_SOCKET
#define POLL WSAPoll
#define CLOSE_SOCKET closesocket
#endif
#ifdef __linux__
#include <poll.h>
#include <unistd.h>
#define RAW_SOCKET int
#define BAD_SOCKET -1
#define POLL poll
#define CLOSE_SOCKET close
#endif
#ifndef HTTP_AMALGAMATION
#include "basic.h"
#endif
int socket_raw_global_init(void);
void socket_raw_global_free(void);
int set_socket_blocking(RAW_SOCKET sock, bool value);
RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog);
#endif // SOCKET_RAW_INCLUDED
+48
View File
@@ -0,0 +1,48 @@
int mutex_init(Mutex *mutex)
{
#ifdef _WIN32
InitializeCriticalSection(mutex); // TODO: mock?
return 0;
#else
if (pthread_mutex_init(mutex, NULL)) // TODO: mock
return -1;
return 0;
#endif
}
int mutex_free(Mutex *mutex)
{
#ifdef _WIN32
DeleteCriticalSection(mutex); // TODO: mock?
return 0;
#else
if (pthread_mutex_destroy(mutex)) // TODO: mock
return -1;
return 0;
#endif
}
int mutex_lock(Mutex *mutex)
{
#ifdef _WIN32
EnterCriticalSection(mutex); // TODO: mock?
return 0;
#else
if (pthread_mutex_lock(mutex)) // TODO: mock
return -1;
return 0;
#endif
}
int mutex_unlock(Mutex *mutex)
{
#ifdef _WIN32
LeaveCriticalSection(mutex); // TODO: mock?
return 0;
#else
if (pthread_mutex_unlock(mutex)) // TODO: mock
return -1;
return 0;
#endif
}
+9
View File
@@ -0,0 +1,9 @@
typedef struct {
// TODO
} Mutex;
int mutex_init(Mutex *mutex);
int mutex_free(Mutex *mutex);
int mutex_lock(Mutex *mutex);
int mutex_unlock(Mutex *mutex);