First version of the refactor
This commit is contained in:
+1
-7
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+282
-55
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user