Refactoring for an improved event loop
This commit is contained in:
@@ -41,12 +41,19 @@ header.save("chttp.h")
|
||||
|
||||
source = Amalgamator()
|
||||
source.append_text("#include \"chttp.h\"\n")
|
||||
|
||||
source.append_file("src/sec.h")
|
||||
source.append_file("src/socket_raw.h")
|
||||
source.append_file("src/socket.h")
|
||||
source.append_file("src/socket_pool.h")
|
||||
source.append_file("src/basic.c")
|
||||
source.append_file("src/parse.c")
|
||||
source.append_file("src/engine.c")
|
||||
source.append_file("src/cert.c")
|
||||
source.append_file("src/sec.c")
|
||||
source.append_file("src/socket_raw.c")
|
||||
source.append_file("src/socket.c")
|
||||
source.append_file("src/socket_pool.c")
|
||||
source.append_file("src/client.c")
|
||||
source.append_file("src/server.c")
|
||||
source.append_file("src/router.c")
|
||||
|
||||
@@ -386,7 +386,7 @@ typedef struct {
|
||||
void *data0;
|
||||
int data1;
|
||||
int data2;
|
||||
} HTTP_RequestHandle;
|
||||
} HTTP_RequestBuilder;
|
||||
|
||||
// Initialize a client object. If something goes wrong,
|
||||
// NULL is returned.
|
||||
@@ -398,7 +398,38 @@ 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_request(HTTP_Client *client, HTTP_RequestHandle *handle);
|
||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder);
|
||||
|
||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Mark the initialization of the request as completed and
|
||||
// perform the request.
|
||||
void http_request_builder_submit(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_Client *client, HTTP_Response *res);
|
||||
|
||||
// Wait for the completion of one request associated to
|
||||
// the client. The handle of the resolved request is returned
|
||||
@@ -411,55 +442,16 @@ int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle);
|
||||
//
|
||||
// Note that calling this function when no requests are
|
||||
// pending is considered an error.
|
||||
int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle);
|
||||
|
||||
// Enable/disable I/O tracing for the specified request.
|
||||
// This must be done when the request is in the initialization
|
||||
// phase.
|
||||
void http_request_trace(HTTP_RequestHandle handle, bool trace);
|
||||
|
||||
// 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_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_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_header(HTTP_RequestHandle handle, HTTP_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_body(HTTP_RequestHandle handle, HTTP_String str);
|
||||
|
||||
// Mark the initialization of the request as completed and
|
||||
// perform the request.
|
||||
void http_request_submit(HTTP_RequestHandle handle);
|
||||
|
||||
// Retrieve the response to the specified request. If the
|
||||
// request hasn't completed or it couldn't be performed
|
||||
// due to an error, NULL is returned. If the request completed,
|
||||
// the parsed response object is returned.
|
||||
//
|
||||
// Note that responses 4xx and 5xx code responses are still
|
||||
// considered as successes from cHTTP's perspective.
|
||||
HTTP_Response *http_request_result(HTTP_RequestHandle handle);
|
||||
|
||||
// Free resources associated to a request. This must be called
|
||||
// after the request has completed.
|
||||
//
|
||||
// TODO: allow aborting pending requests
|
||||
void http_request_free(HTTP_RequestHandle handle);
|
||||
int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data);
|
||||
|
||||
// TODO: comment
|
||||
HTTP_Response *http_get(HTTP_String url,
|
||||
HTTP_String *headers, int num_headers,
|
||||
HTTP_RequestHandle *phandle);
|
||||
HTTP_String *headers, int num_headers);
|
||||
|
||||
// TODO: comment
|
||||
HTTP_Response *http_post(HTTP_String url,
|
||||
HTTP_String *headers, int num_headers,
|
||||
HTTP_String body, HTTP_RequestHandle *phandle);
|
||||
HTTP_String body);
|
||||
|
||||
#endif // CLIENT_INCLUDED
|
||||
|
||||
|
||||
@@ -13,17 +13,11 @@ int main(void)
|
||||
HTTP_STR("User-Agent: cHTTP"),
|
||||
};
|
||||
|
||||
// Request handle that will be necessary to
|
||||
// free the request's resources when we are
|
||||
// done with the result.
|
||||
HTTP_RequestHandle handle;
|
||||
|
||||
// Perform the request. This will block the thread
|
||||
// until an error occurs or the request completes.
|
||||
HTTP_Response *res = http_get(
|
||||
HTTP_STR("http://example.com/index.html"),
|
||||
headers, HTTP_COUNT(headers),
|
||||
&handle
|
||||
headers, HTTP_COUNT(headers)
|
||||
);
|
||||
|
||||
// The http_get function returns NULL if the request
|
||||
@@ -51,7 +45,7 @@ int main(void)
|
||||
|
||||
// When we are done reading from the response object
|
||||
// we must free the request's resources.
|
||||
http_request_free(handle);
|
||||
http_request_free(res);
|
||||
|
||||
// All done. Deinitialize the library.
|
||||
http_global_free();
|
||||
|
||||
@@ -12,40 +12,30 @@ int main(int argc, char **argv) {
|
||||
http_global_init();
|
||||
HTTP_Client *client = http_client_init();
|
||||
|
||||
HTTP_RequestHandle reqs[100];
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
int k = i-1;
|
||||
if (http_client_request(client, &reqs[k]) < 0) {
|
||||
HTTP_RequestBuilder builder;
|
||||
if (http_client_get_builder(client, &builder) < 0) {
|
||||
printf("request creation error\n");
|
||||
return -1;
|
||||
}
|
||||
http_request_line(reqs[k], HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) });
|
||||
http_request_submit(reqs[k]);
|
||||
http_request_builer_line(builder, HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) });
|
||||
http_request_builder_submit(builder);
|
||||
printf("request submitted\n");
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
if (http_client_wait(client, NULL) < 0) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
||||
HTTP_Response *res;
|
||||
if (http_client_wait(client, &res, NULL) < 0) {
|
||||
printf("request wait error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("all requests completed\n");
|
||||
printf("Status: %d\n", res->status);
|
||||
printf("Body: %.*s\n", HTTP_UNPACK(res->body));
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
||||
HTTP_Response *result = http_request_result(reqs[i-1]);
|
||||
if (!result) {
|
||||
fprintf(stderr, "No result from HTTP request\n");
|
||||
http_request_free(reqs[i-1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Status: %d\n", result->status);
|
||||
printf("Body: %.*s\n", HTTP_UNPACK(result->body));
|
||||
|
||||
http_request_free(reqs[i-1]);
|
||||
http_request_free(res);
|
||||
}
|
||||
|
||||
http_client_free(client);
|
||||
|
||||
@@ -139,31 +139,26 @@ int main(int argc, char **argv)
|
||||
return -1;
|
||||
}
|
||||
|
||||
HTTP_RequestHandle req;
|
||||
int ret = http_client_request(client, &req);
|
||||
HTTP_RequestBuilder builder;
|
||||
int ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0) {
|
||||
printf("Couldn't start request\n");
|
||||
http_client_free(client);
|
||||
return -1;
|
||||
}
|
||||
http_request_line(req, HTTP_METHOD_GET, start_url);
|
||||
http_request_header(req, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_submit(req);
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, start_url);
|
||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_RequestHandle req;
|
||||
ret = http_client_wait(client, &req);
|
||||
HTTP_Response *res;
|
||||
ret = http_client_wait(client, &res, NULL);
|
||||
if (ret < 0) {
|
||||
// TODO
|
||||
return -1;
|
||||
}
|
||||
|
||||
HTTP_Response *res = http_request_result(req);
|
||||
if (res == NULL) {
|
||||
http_request_free(req);
|
||||
continue; // Request didn't complete
|
||||
}
|
||||
HTTP_String body = res->body;
|
||||
|
||||
int cursor = 0;
|
||||
@@ -180,14 +175,14 @@ int main(int argc, char **argv)
|
||||
printf("Fetching " GRN "%.*s" RST "\n", HTTP_UNPACK(url));
|
||||
add_to_crawled_list(url);
|
||||
|
||||
HTTP_RequestHandle req;
|
||||
ret = http_client_request(client, &req);
|
||||
HTTP_RequestBuilder builder;
|
||||
ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
http_request_line(req, HTTP_METHOD_GET, url);
|
||||
http_request_header(req, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_submit(req);
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, url);
|
||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_builder_submit(builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+138
-185
@@ -16,8 +16,8 @@
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "client.h"
|
||||
#include "socket.h"
|
||||
#include "engine.h"
|
||||
#include "socket_pool.h"
|
||||
#endif
|
||||
|
||||
#define CLIENT_MAX_CONNS 256
|
||||
@@ -32,13 +32,16 @@ typedef enum {
|
||||
typedef struct {
|
||||
ClientConnectionState state;
|
||||
uint16_t gen;
|
||||
Socket socket;
|
||||
HTTP_Engine engine;
|
||||
SocketHandle sock;
|
||||
HTTP_Engine eng;
|
||||
bool trace;
|
||||
void* user_data;
|
||||
} ClientConnection;
|
||||
|
||||
struct HTTP_Client {
|
||||
SocketGroup group;
|
||||
|
||||
SocketPool *socket_pool;
|
||||
|
||||
int num_conns;
|
||||
ClientConnection conns[CLIENT_MAX_CONNS];
|
||||
|
||||
@@ -60,26 +63,19 @@ static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *da
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void http_global_init(void)
|
||||
{
|
||||
socket_global_init();
|
||||
}
|
||||
|
||||
void http_global_free(void)
|
||||
{
|
||||
socket_global_free();
|
||||
}
|
||||
|
||||
HTTP_Client *http_client_init(void)
|
||||
{
|
||||
HTTP_Client *client = malloc(sizeof(HTTP_Client));
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
if (socket_group_init(&client->group) < 0) {
|
||||
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;
|
||||
@@ -104,11 +100,11 @@ void http_client_free(HTTP_Client *client)
|
||||
// TODO
|
||||
}
|
||||
|
||||
socket_group_free(&client->group);
|
||||
socket_pool_free(client->socket_pool);
|
||||
free(client);
|
||||
}
|
||||
|
||||
int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle)
|
||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder)
|
||||
{
|
||||
if (client->num_conns == CLIENT_MAX_CONNS)
|
||||
return -1;
|
||||
@@ -117,132 +113,102 @@ int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle)
|
||||
while (client->conns[i].state != CLIENT_CONNECTION_FREE)
|
||||
i++;
|
||||
|
||||
client->conns[i].user_data = NULL;
|
||||
client->conns[i].trace = false;
|
||||
client->conns[i].state = CLIENT_CONNECTION_INIT;
|
||||
http_engine_init(&client->conns[i].engine, 1, client_memfunc, NULL);
|
||||
http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL);
|
||||
|
||||
client->num_conns++;
|
||||
|
||||
*handle = (HTTP_RequestHandle) { client, i, client->conns[i].gen };
|
||||
*builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen };
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_connection_update(ClientConnection *conn)
|
||||
int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data)
|
||||
{
|
||||
HTTP_ASSERT(conn->state == CLIENT_CONNECTION_WAIT);
|
||||
while (client->ready_count == 0) {
|
||||
|
||||
socket_update(&conn->socket);
|
||||
SocketEvent event = socket_pool_wait(client->socket_pool);
|
||||
switch (event.type) {
|
||||
|
||||
while (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) {
|
||||
case SOCKET_EVENT_DIED:
|
||||
{
|
||||
ClientConnection *conn = event.user_data;
|
||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
||||
client->ready[tail] = conn - client->conns;
|
||||
client->ready_count++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_EVENT_READY:
|
||||
{
|
||||
ClientConnection *conn = event.user_data;
|
||||
|
||||
HTTP_EngineState engine_state;
|
||||
|
||||
engine_state = http_engine_state(&conn->engine);
|
||||
engine_state = http_engine_state(&conn->eng);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) {
|
||||
int len;
|
||||
char *buf;
|
||||
buf = http_engine_recvbuf(&conn->engine, &len);
|
||||
buf = http_engine_recvbuf(&conn->eng, &len);
|
||||
if (buf) {
|
||||
int ret = socket_read(&conn->socket, buf, len);
|
||||
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->engine, 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->engine, &len);
|
||||
buf = http_engine_sendbuf(&conn->eng, &len);
|
||||
if (buf) {
|
||||
int ret = socket_write(&conn->socket, buf, len);
|
||||
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->engine, ret);
|
||||
http_engine_sendack(&conn->eng, ret);
|
||||
}
|
||||
}
|
||||
|
||||
engine_state = http_engine_state(&conn->engine);
|
||||
engine_state = http_engine_state(&conn->eng);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
|
||||
engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
|
||||
socket_close(&conn->socket);
|
||||
socket_pool_close(client->socket_pool, conn->sock);
|
||||
}
|
||||
break;
|
||||
|
||||
if (socket_state(&conn->socket) == SOCKET_STATE_DIED)
|
||||
conn->state = CLIENT_CONNECTION_DONE;
|
||||
}
|
||||
|
||||
int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle)
|
||||
{
|
||||
while (client->ready_count == 0) {
|
||||
|
||||
int num_polled = 0;
|
||||
int indices[CLIENT_MAX_CONNS];
|
||||
struct pollfd polled[CLIENT_MAX_CONNS];
|
||||
|
||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
||||
|
||||
HTTP_ASSERT(i < CLIENT_MAX_CONNS);
|
||||
ClientConnection *conn = &client->conns[i];
|
||||
|
||||
if (conn->state == CLIENT_CONNECTION_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
int events = 0;
|
||||
if (conn->state == CLIENT_CONNECTION_WAIT) {
|
||||
switch (conn->socket.event) {
|
||||
case SOCKET_WANT_READ : events = POLLIN; break;
|
||||
case SOCKET_WANT_WRITE: events = POLLOUT; break;
|
||||
case SOCKET_WANT_NONE : events = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (events) {
|
||||
indices[num_polled] = i;
|
||||
polled[num_polled].fd = conn->socket.fd;
|
||||
polled[num_polled].events = events;
|
||||
polled[num_polled].revents = 0;
|
||||
num_polled++;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_polled == 0)
|
||||
case SOCKET_EVENT_ERROR:
|
||||
return -1;
|
||||
|
||||
POLL(polled, num_polled, -1);
|
||||
|
||||
for (int i = 0; i < num_polled; i++) {
|
||||
|
||||
int connidx = indices[i];
|
||||
ClientConnection *conn = &client->conns[connidx];
|
||||
|
||||
if (conn->state != CLIENT_CONNECTION_WAIT)
|
||||
continue;
|
||||
|
||||
if (polled[i].revents == 0)
|
||||
continue;
|
||||
|
||||
// TODO: handle error revents
|
||||
|
||||
client_connection_update(conn);
|
||||
|
||||
if (conn->state == CLIENT_CONNECTION_DONE) {
|
||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
||||
client->ready[tail] = connidx;
|
||||
client->ready_count++;
|
||||
}
|
||||
case SOCKET_EVENT_SIGNAL:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int index = client->ready[client->ready_head];
|
||||
client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS;
|
||||
client->ready_count--;
|
||||
*handle = (HTTP_RequestHandle) { client, index, client->conns[index].gen };
|
||||
|
||||
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--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ClientConnection *handle2clientconn(HTTP_RequestHandle handle)
|
||||
static ClientConnection *builder2conn(HTTP_RequestBuilder handle)
|
||||
{
|
||||
if (handle.data0 == NULL)
|
||||
return NULL;
|
||||
@@ -260,9 +226,20 @@ static ClientConnection *handle2clientconn(HTTP_RequestHandle handle)
|
||||
return conn;
|
||||
}
|
||||
|
||||
void http_request_trace(HTTP_RequestHandle handle, bool trace)
|
||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data)
|
||||
{
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
ClientConnection *conn = builder2conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
conn->user_data = user_data;
|
||||
}
|
||||
|
||||
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace)
|
||||
{
|
||||
ClientConnection *conn = builder2conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
@@ -271,15 +248,15 @@ void http_request_trace(HTTP_RequestHandle handle, bool trace)
|
||||
conn->trace = trace;
|
||||
}
|
||||
|
||||
void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_String url)
|
||||
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url)
|
||||
{
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
ClientConnection *conn = builder2conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
HTTP_Client *client = handle.data0;
|
||||
HTTP_Client *client = builder.data0;
|
||||
|
||||
HTTP_URL parsed_url;
|
||||
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
|
||||
@@ -304,77 +281,76 @@ void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_Strin
|
||||
port = 80;
|
||||
}
|
||||
|
||||
SocketGroup *group = secure ? &client->group : NULL;
|
||||
switch (parsed_url.authority.host.mode) {
|
||||
case HTTP_HOST_MODE_IPV4: socket_connect_ipv4(&conn->socket, group, parsed_url.authority.host.ipv4, port); break;
|
||||
case HTTP_HOST_MODE_IPV6: socket_connect_ipv6(&conn->socket, group, parsed_url.authority.host.ipv6, port); break;
|
||||
case HTTP_HOST_MODE_NAME: socket_connect (&conn->socket, group, parsed_url.authority.host.name, port); break;
|
||||
case HTTP_HOST_MODE_IPV4: socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn->user_data); break;
|
||||
case HTTP_HOST_MODE_IPV6: socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn->user_data); break;
|
||||
case HTTP_HOST_MODE_NAME: socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn->user_data); break;
|
||||
|
||||
case HTTP_HOST_MODE_VOID:
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
http_engine_url(&conn->engine, method, url, 1);
|
||||
http_engine_url(&conn->eng, method, url, 1);
|
||||
}
|
||||
|
||||
void http_request_header(HTTP_RequestHandle handle, HTTP_String str)
|
||||
void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str)
|
||||
{
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
ClientConnection *conn = builder2conn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
http_engine_header(&conn->engine, str);
|
||||
http_engine_header(&conn->eng, str);
|
||||
}
|
||||
|
||||
void http_request_body(HTTP_RequestHandle handle, HTTP_String str)
|
||||
void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str)
|
||||
{
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
ClientConnection *conn = builder2conn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
http_engine_body(&conn->engine, str);
|
||||
http_engine_body(&conn->eng, str);
|
||||
}
|
||||
|
||||
void http_request_submit(HTTP_RequestHandle handle)
|
||||
void http_request_builder_submit(HTTP_RequestBuilder handle)
|
||||
{
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
ClientConnection *conn = builder2conn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
http_engine_done(&conn->engine);
|
||||
// TODO: invalidate the handle
|
||||
|
||||
http_engine_done(&conn->eng);
|
||||
conn->state = CLIENT_CONNECTION_WAIT;
|
||||
}
|
||||
|
||||
HTTP_Response *http_request_result(HTTP_RequestHandle handle)
|
||||
void http_response_free(HTTP_Client *client, HTTP_Response *res)
|
||||
{
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
if (conn == NULL)
|
||||
return NULL;
|
||||
if (conn->state != CLIENT_CONNECTION_DONE)
|
||||
return NULL;
|
||||
HTTP_EngineState engine_state = http_engine_state(&conn->engine);
|
||||
if (engine_state != HTTP_ENGINE_STATE_CLIENT_READY)
|
||||
return NULL;
|
||||
return http_engine_getres(&conn->engine);
|
||||
}
|
||||
ClientConnection *conn = NULL;
|
||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
||||
|
||||
void http_request_free(HTTP_RequestHandle handle)
|
||||
{
|
||||
HTTP_Client *client = handle.data0;
|
||||
ClientConnection *conn = handle2clientconn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_DONE)
|
||||
return;
|
||||
http_engine_free(&conn->engine);
|
||||
socket_free(&conn->socket);
|
||||
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--;
|
||||
}
|
||||
@@ -388,72 +364,49 @@ static HTTP_Client *get_default_client(void)
|
||||
return default_client___;
|
||||
}
|
||||
|
||||
HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_RequestHandle *phandle)
|
||||
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_RequestHandle handle;
|
||||
int ret = http_client_request(client, &handle);
|
||||
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;
|
||||
|
||||
http_request_line(handle, HTTP_METHOD_GET, url);
|
||||
|
||||
for (int i = 0; i < num_headers; i++)
|
||||
http_request_header(handle, headers[i]);
|
||||
|
||||
http_request_submit(handle);
|
||||
|
||||
ret = http_client_wait(client, NULL); // TODO: it's assumed there is only one request pending
|
||||
if (ret < 0) {
|
||||
http_request_free(handle); // TODO: currently free only works on completed request handles
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HTTP_Response *res = http_request_result(handle);
|
||||
if (res == NULL) {
|
||||
http_request_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*phandle = handle;
|
||||
return res;
|
||||
}
|
||||
|
||||
HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body, HTTP_RequestHandle *phandle)
|
||||
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_RequestHandle handle;
|
||||
int ret = http_client_request(client, &handle);
|
||||
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_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;
|
||||
|
||||
http_request_line(handle, HTTP_METHOD_GET, url);
|
||||
|
||||
for (int i = 0; i < num_headers; i++)
|
||||
http_request_header(handle, headers[i]);
|
||||
|
||||
http_request_body(handle, body);
|
||||
|
||||
http_request_submit(handle);
|
||||
|
||||
ret = http_client_wait(client, NULL); // TODO: it's assumed there is only one request pending
|
||||
if (ret < 0) {
|
||||
http_request_free(handle); // TODO: currently free only works on completed request handles
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HTTP_Response *res = http_request_result(handle);
|
||||
if (res == NULL) {
|
||||
http_request_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*phandle = handle;
|
||||
return res;
|
||||
}
|
||||
+36
-44
@@ -28,7 +28,7 @@ typedef struct {
|
||||
void *data0;
|
||||
int data1;
|
||||
int data2;
|
||||
} HTTP_RequestHandle;
|
||||
} HTTP_RequestBuilder;
|
||||
|
||||
// Initialize a client object. If something goes wrong,
|
||||
// NULL is returned.
|
||||
@@ -40,7 +40,38 @@ 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_request(HTTP_Client *client, HTTP_RequestHandle *handle);
|
||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder);
|
||||
|
||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Mark the initialization of the request as completed and
|
||||
// perform the request.
|
||||
void http_request_builder_submit(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_Client *client, HTTP_Response *res);
|
||||
|
||||
// Wait for the completion of one request associated to
|
||||
// the client. The handle of the resolved request is returned
|
||||
@@ -53,54 +84,15 @@ int http_client_request(HTTP_Client *client, HTTP_RequestHandle *handle);
|
||||
//
|
||||
// Note that calling this function when no requests are
|
||||
// pending is considered an error.
|
||||
int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle);
|
||||
|
||||
// Enable/disable I/O tracing for the specified request.
|
||||
// This must be done when the request is in the initialization
|
||||
// phase.
|
||||
void http_request_trace(HTTP_RequestHandle handle, bool trace);
|
||||
|
||||
// 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_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_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_header(HTTP_RequestHandle handle, HTTP_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_body(HTTP_RequestHandle handle, HTTP_String str);
|
||||
|
||||
// Mark the initialization of the request as completed and
|
||||
// perform the request.
|
||||
void http_request_submit(HTTP_RequestHandle handle);
|
||||
|
||||
// Retrieve the response to the specified request. If the
|
||||
// request hasn't completed or it couldn't be performed
|
||||
// due to an error, NULL is returned. If the request completed,
|
||||
// the parsed response object is returned.
|
||||
//
|
||||
// Note that responses 4xx and 5xx code responses are still
|
||||
// considered as successes from cHTTP's perspective.
|
||||
HTTP_Response *http_request_result(HTTP_RequestHandle handle);
|
||||
|
||||
// Free resources associated to a request. This must be called
|
||||
// after the request has completed.
|
||||
//
|
||||
// TODO: allow aborting pending requests
|
||||
void http_request_free(HTTP_RequestHandle handle);
|
||||
int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data);
|
||||
|
||||
// TODO: comment
|
||||
HTTP_Response *http_get(HTTP_String url,
|
||||
HTTP_String *headers, int num_headers,
|
||||
HTTP_RequestHandle *phandle);
|
||||
HTTP_String *headers, int num_headers);
|
||||
|
||||
// TODO: comment
|
||||
HTTP_Response *http_post(HTTP_String url,
|
||||
HTTP_String *headers, int num_headers,
|
||||
HTTP_String body, HTTP_RequestHandle *phandle);
|
||||
HTTP_String body);
|
||||
|
||||
#endif // CLIENT_INCLUDED
|
||||
@@ -0,0 +1,204 @@
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "sec.h"
|
||||
#endif
|
||||
|
||||
#ifndef HTTPS_ENABLED
|
||||
|
||||
int secure_context_init_as_client(SecureContext *sec)
|
||||
{
|
||||
(void) sec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int secure_context_init_as_server(SecureContext *sec,
|
||||
char *cert_file, int cert_file_len,
|
||||
char *key_file, int key_file_len)
|
||||
{
|
||||
(void) sec;
|
||||
(void) cert_file;
|
||||
(void) cert_file_len;
|
||||
(void) key_file;
|
||||
(void) key_file_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int secure_context_add_cert(SecureContext *sec,
|
||||
char *domain, int domain_len, char *cert_file,
|
||||
int cert_file_len, char *key_file, int key_file_len)
|
||||
{
|
||||
(void) sec;
|
||||
(void) domain;
|
||||
(void) domain_len;
|
||||
(void) cert_file;
|
||||
(void) cert_file_len;
|
||||
(void) key_file;
|
||||
(void) key_file_len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void secure_context_free(SecureContext *sec)
|
||||
{
|
||||
(void) sec;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
char *cert_file, int cert_file_len,
|
||||
char *key_file, int key_file_len)
|
||||
{
|
||||
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||
|
||||
static char cert_buffer[1024];
|
||||
if (cert_file_len >= (int) sizeof(cert_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert_buffer, cert_file, cert_file_len);
|
||||
cert_buffer[cert_file_len] = '\0';
|
||||
|
||||
// Copy private key file path to static buffer
|
||||
static char key_buffer[1024];
|
||||
if (key_file_len >= (int) sizeof(key_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(key_buffer, key_file, 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,
|
||||
char *domain, int domain_len, char *cert_file,
|
||||
int cert_file_len, char *key_file, int key_file_len)
|
||||
{
|
||||
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);
|
||||
|
||||
static char cert_buffer[1024];
|
||||
if (cert_file_len >= (int) sizeof(cert_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert_buffer, cert_file, cert_file_len);
|
||||
cert_buffer[cert_file_len] = '\0';
|
||||
|
||||
static char key_buffer[1024];
|
||||
if (key_file_len >= (int) sizeof(key_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(key_buffer, key_file, 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, domain_len);
|
||||
cert->domain[domain_len] = '\0';
|
||||
cert->ctx = ctx;
|
||||
sec->num_certs++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef SEC_INCLUDED
|
||||
#define SEC_INCLUDED
|
||||
|
||||
#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
|
||||
|
||||
int secure_context_init_as_client(SecureContext *sec);
|
||||
|
||||
int secure_context_init_as_server(SecureContext *sec,
|
||||
char *cert_file, int cert_file_len,
|
||||
char *key_file, int key_file_len);
|
||||
|
||||
int secure_context_add_cert(SecureContext *sec,
|
||||
char *domain, int domain_len, char *cert_file,
|
||||
int cert_file_len, char *key_file, int key_file_len);
|
||||
|
||||
void secure_context_free(SecureContext *sec);
|
||||
|
||||
#endif // SEC_INCLUDED
|
||||
+53
-138
@@ -5,8 +5,8 @@
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "engine.h"
|
||||
#include "socket.h"
|
||||
#include "server.h"
|
||||
#include "socket_pool.h"
|
||||
#endif
|
||||
|
||||
#define MAX_CONNS (1<<10)
|
||||
@@ -14,15 +14,13 @@
|
||||
typedef struct {
|
||||
bool used;
|
||||
uint16_t gen;
|
||||
Socket socket;
|
||||
HTTP_Engine engine;
|
||||
SocketHandle sock;
|
||||
} Connection;
|
||||
|
||||
struct HTTP_Server {
|
||||
SocketGroup group;
|
||||
|
||||
SOCKET_TYPE listen_fd;
|
||||
SOCKET_TYPE secure_fd;
|
||||
SocketPool *socket_pool;
|
||||
|
||||
int num_conns;
|
||||
Connection conns[MAX_CONNS];
|
||||
@@ -38,7 +36,7 @@ HTTP_Server *http_server_init(HTTP_String addr, uint16_t port)
|
||||
}
|
||||
|
||||
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
|
||||
uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key)
|
||||
uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
HTTP_Server *server = malloc(sizeof(HTTP_Server));
|
||||
if (server == NULL)
|
||||
@@ -46,42 +44,13 @@ HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
|
||||
|
||||
int backlog = 32;
|
||||
bool reuse_addr = true;
|
||||
|
||||
if (port == 0 && secure_port == 0) {
|
||||
// You must have at least one!
|
||||
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;
|
||||
}
|
||||
|
||||
if (port == 0)
|
||||
server->listen_fd = BAD_SOCKET;
|
||||
else {
|
||||
server->listen_fd = listen_socket(addr, port, reuse_addr, backlog);
|
||||
if (server->listen_fd == BAD_SOCKET) {
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (secure_port == 0)
|
||||
server->secure_fd = BAD_SOCKET;
|
||||
else {
|
||||
|
||||
if (socket_group_init_server(&server->group, cert_key, private_key) < 0) {
|
||||
CLOSE_SOCKET(server->listen_fd);
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
server->secure_fd = listen_socket(addr, secure_port, reuse_addr, backlog);
|
||||
if (server->secure_fd == BAD_SOCKET) {
|
||||
socket_group_free(&server->group);
|
||||
CLOSE_SOCKET(server->listen_fd);
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
server->socket_pool = socket_pool;
|
||||
server->num_conns = 0;
|
||||
server->ready_head = 0;
|
||||
server->ready_count = 0;
|
||||
@@ -105,16 +74,13 @@ void http_server_free(HTTP_Server *server)
|
||||
// TODO
|
||||
}
|
||||
|
||||
CLOSE_SOCKET(server->secure_fd);
|
||||
CLOSE_SOCKET(server->listen_fd);
|
||||
if (server->secure_fd != BAD_SOCKET)
|
||||
socket_group_free(&server->group);
|
||||
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)
|
||||
{
|
||||
return socket_group_add_domain(&server->group, domain, cert_file, key_file);
|
||||
return socket_pool_add_cert(server->socket_pool, domain.ptr, domain.len, cert_file.ptr, cert_file.len, key_file.ptr, key_file.len);
|
||||
}
|
||||
|
||||
static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
|
||||
@@ -133,91 +99,45 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl
|
||||
{
|
||||
while (server->ready_count == 0) {
|
||||
|
||||
int num_polled = 0;
|
||||
struct pollfd polled[MAX_CONNS+2];
|
||||
int indices[MAX_CONNS+2];
|
||||
SocketEvent event = socket_pool_wait(server->socket_pool);
|
||||
switch (event.type) {
|
||||
|
||||
if (server->num_conns < MAX_CONNS) {
|
||||
case SOCKET_EVENT_DIED:
|
||||
{
|
||||
Connection *conn = event.user_data;
|
||||
HTTP_ASSERT(conn);
|
||||
|
||||
if (server->listen_fd != BAD_SOCKET) {
|
||||
polled[num_polled].fd = server->listen_fd;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
indices[num_polled] = -1;
|
||||
num_polled++;
|
||||
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;
|
||||
}
|
||||
|
||||
if (server->secure_fd != BAD_SOCKET) {
|
||||
polled[num_polled].fd = server->secure_fd;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
indices[num_polled] = -1;
|
||||
num_polled++;
|
||||
}
|
||||
}
|
||||
int i = 0;
|
||||
while (server->conns[i].used)
|
||||
i++;
|
||||
|
||||
for (int i = 0, j = 0; i < server->num_conns; i++) {
|
||||
|
||||
Connection *conn = &server->conns[i];
|
||||
|
||||
if (!conn->used)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
int events = 0;
|
||||
switch (conn->socket.event) {
|
||||
case SOCKET_WANT_NONE: events = 0; break;
|
||||
case SOCKET_WANT_READ: events = POLLIN; break;
|
||||
case SOCKET_WANT_WRITE: events = POLLOUT; break;
|
||||
}
|
||||
|
||||
if (events) {
|
||||
polled[num_polled].fd = conn->socket.fd;
|
||||
polled[num_polled].events = events;
|
||||
polled[num_polled].revents = 0;
|
||||
indices[num_polled] = i;
|
||||
num_polled++;
|
||||
}
|
||||
}
|
||||
|
||||
int timeout = -1;
|
||||
POLL(polled, num_polled, timeout);
|
||||
|
||||
for (int i = 0; i < num_polled; i++) {
|
||||
|
||||
if (polled[i].fd == server->listen_fd || polled[i].fd == server->secure_fd) {
|
||||
|
||||
bool secure = false;
|
||||
if (polled[i].fd == server->secure_fd)
|
||||
secure = true;
|
||||
|
||||
if ((polled[i].revents & POLLIN) && server->num_conns < MAX_CONNS) {
|
||||
|
||||
SOCKET_TYPE new_fd = accept(polled[i].fd, NULL, NULL);
|
||||
if (new_fd == BAD_SOCKET) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
while (server->conns[k].used)
|
||||
k++;
|
||||
|
||||
server->conns[k].used = true;
|
||||
socket_accept(&server->conns[k].socket, secure ? &server->group : NULL, new_fd);
|
||||
|
||||
http_engine_init(&server->conns[k].engine, 0, server_memfunc, NULL);
|
||||
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++;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
int connidx = indices[i];
|
||||
Connection *conn = &server->conns[connidx];
|
||||
|
||||
socket_update(&conn->socket);
|
||||
|
||||
if (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) {
|
||||
|
||||
switch (http_engine_state(&conn->engine)) {
|
||||
|
||||
int len;
|
||||
@@ -226,7 +146,7 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF:
|
||||
buf = http_engine_recvbuf(&conn->engine, &len);
|
||||
if (buf) {
|
||||
int ret = socket_read(&conn->socket, buf, len);
|
||||
int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len);
|
||||
http_engine_recvack(&conn->engine, ret);
|
||||
}
|
||||
break;
|
||||
@@ -234,7 +154,7 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF:
|
||||
buf = http_engine_sendbuf(&conn->engine, &len);
|
||||
if (buf) {
|
||||
int ret = socket_write(&conn->socket, buf, len);
|
||||
int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len);
|
||||
http_engine_sendack(&conn->engine, ret);
|
||||
}
|
||||
break;
|
||||
@@ -249,27 +169,25 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS:
|
||||
tail = (server->ready_head + server->ready_count) % MAX_CONNS;
|
||||
server->ready[tail] = connidx;
|
||||
server->ready[tail] = conn - server->conns;
|
||||
server->ready_count++;
|
||||
break;
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_CLOSED:
|
||||
socket_close(&conn->socket);
|
||||
socket_pool_close(server->socket_pool, conn->sock);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
case SOCKET_EVENT_ERROR:
|
||||
return -1;
|
||||
|
||||
if (socket_state(&conn->socket) == SOCKET_STATE_DIED) {
|
||||
socket_free(&conn->socket);
|
||||
http_engine_free(&conn->engine);
|
||||
conn->used = false;
|
||||
server->num_conns--;
|
||||
}
|
||||
}
|
||||
case SOCKET_EVENT_SIGNAL:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +300,6 @@ void http_response_done(HTTP_ResponseHandle res)
|
||||
server->ready_count++;
|
||||
}
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) {
|
||||
socket_close(&conn->socket);
|
||||
http_engine_free(&conn->engine);
|
||||
server->num_conns--;
|
||||
}
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
|
||||
socket_pool_close(server->socket_pool, conn->sock);
|
||||
}
|
||||
+388
-613
File diff suppressed because it is too large
Load Diff
+52
-143
@@ -1,166 +1,75 @@
|
||||
#ifndef SOCKET_INCLUDED
|
||||
#define SOCKET_INCLUDED
|
||||
// This is a socket abstraction module for non-blocking TCP and TLS sockets.
|
||||
//
|
||||
// Sockets may be in a number of states based on if they are plain TCP or TLS
|
||||
// sockets. Users generally only care about when the connection is established
|
||||
// or is terminated.
|
||||
//
|
||||
// Sockets can be created by connecting to a server using one of these:
|
||||
//
|
||||
// socket_connect
|
||||
// socket_connect_ipv4
|
||||
// socket_connect_ipv6
|
||||
//
|
||||
// They allow connecting to a remote host by specifying its name, of IP address.
|
||||
// Or by interning a socket accepted by a listening socket:
|
||||
//
|
||||
// socket_accept
|
||||
//
|
||||
// after creation, the event field will hold one of the values:
|
||||
//
|
||||
// SOCKET_WANT_READ
|
||||
// SOCKET_WANT_WRITE
|
||||
//
|
||||
// Which respectively mean that the socket object needs to read or write
|
||||
// from the underlying socket, and to do so non-blockingly, the caller needs
|
||||
// to wait for the socket being ready for that operation. This is one way
|
||||
// to do it:
|
||||
//
|
||||
// // Translate the socket event field to poll() flags
|
||||
// int events;
|
||||
// if (sock.event == SOCKET_WANT_READ)
|
||||
// events = POLLIN;
|
||||
// else if (sock.event == SOCKET_WANT_WRITE)
|
||||
// events = POLLOUT;
|
||||
//
|
||||
// // block until the socket is ready
|
||||
// struct pollfd buf;
|
||||
// buf.fd = sock.fd;
|
||||
// buf.events = events;
|
||||
// buf.revents = 0;
|
||||
// poll(&buf, 1, -1);
|
||||
//
|
||||
// whenever a socket is ready, the user must call the socket_update
|
||||
// function. Then, if the socket is in the SOCKET_STATE_ESTABLISHED_READY
|
||||
// state, the user can call one of
|
||||
//
|
||||
// socket_close
|
||||
// socket_read
|
||||
// socket_write
|
||||
//
|
||||
// At any point the socket could reach the SOCKET_STATE_DIED state,
|
||||
// which means the user needs to call socket_free to free the socket
|
||||
// as it's not unusable.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#define SOCKET_TYPE SOCKET
|
||||
#define BAD_SOCKET INVALID_SOCKET
|
||||
#define POLL WSAPoll
|
||||
#define CLOSE_SOCKET closesocket
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
#define SOCKET_TYPE int
|
||||
#define BAD_SOCKET -1
|
||||
#define POLL poll
|
||||
#define CLOSE_SOCKET close
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#endif
|
||||
// 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"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int is_ipv6;
|
||||
union {
|
||||
HTTP_IPv4 ipv4;
|
||||
HTTP_IPv6 ipv6;
|
||||
} addr;
|
||||
} AddrInfo;
|
||||
typedef struct PendingConnect PendingConnect;
|
||||
|
||||
// These should only be relevant to socket.c
|
||||
typedef enum {
|
||||
SOCKET_STATE_PENDING,
|
||||
SOCKET_STATE_CONNECTING,
|
||||
SOCKET_STATE_CONNECTED,
|
||||
SOCKET_STATE_ACCEPTED,
|
||||
SOCKET_STATE_FREE,
|
||||
SOCKET_STATE_DIED,
|
||||
SOCKET_STATE_ESTABLISHED_WAIT,
|
||||
SOCKET_STATE_ESTABLISHED_READY,
|
||||
SOCKET_STATE_PENDING,
|
||||
SOCKET_STATE_ACCEPTED,
|
||||
SOCKET_STATE_CONNECTED,
|
||||
SOCKET_STATE_CONNECTING,
|
||||
SOCKET_STATE_SHUTDOWN,
|
||||
SOCKET_STATE_DIED
|
||||
} SocketState;
|
||||
|
||||
typedef enum {
|
||||
SOCKET_WANT_NONE,
|
||||
SOCKET_WANT_READ,
|
||||
SOCKET_WANT_WRITE,
|
||||
} SocketWantEvent;
|
||||
|
||||
typedef struct {
|
||||
SocketState state;
|
||||
SocketWantEvent event;
|
||||
SOCKET_TYPE fd;
|
||||
#if HTTPS_ENABLED
|
||||
|
||||
RAW_SOCKET raw;
|
||||
int events;
|
||||
|
||||
void *user_data;
|
||||
PendingConnect *pending_connect;
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
SSL *ssl;
|
||||
SSL_CTX *ssl_ctx;
|
||||
#endif
|
||||
AddrInfo *addr_list;
|
||||
int addr_count;
|
||||
int addr_cursor;
|
||||
char *hostname;
|
||||
uint16_t port;
|
||||
|
||||
SecureContext *sec;
|
||||
|
||||
} Socket;
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
typedef struct {
|
||||
char name[128];
|
||||
SSL_CTX *ssl_ctx;
|
||||
} Domain;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
#ifdef HTTPS_ENABLED
|
||||
SSL_CTX *ssl_ctx;
|
||||
int num_domains;
|
||||
int max_domains;
|
||||
Domain *domains;
|
||||
#endif
|
||||
} SocketGroup;
|
||||
|
||||
SOCKET_TYPE listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog);
|
||||
|
||||
|
||||
void socket_global_init (void);
|
||||
void socket_global_free (void);
|
||||
int socket_group_init (SocketGroup *group);
|
||||
int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file);
|
||||
int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_key, HTTP_String private_key);
|
||||
void socket_group_free (SocketGroup *group);
|
||||
SocketState socket_state (Socket *sock);
|
||||
void socket_accept (Socket *sock, SocketGroup *group, SOCKET_TYPE fd);
|
||||
void socket_connect (Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port);
|
||||
void socket_connect_ipv4 (Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port);
|
||||
void socket_connect_ipv6 (Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port);
|
||||
void socket_update (Socket *sock);
|
||||
int socket_read (Socket *sock, char *dst, int max);
|
||||
int socket_write (Socket *sock, char *src, int len);
|
||||
void socket_close (Socket *sock);
|
||||
void socket_free (Socket *sock);
|
||||
int socket_wait (Socket **socks, int num_socks);
|
||||
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);
|
||||
|
||||
#endif // SOCKET_INCLUDED
|
||||
@@ -0,0 +1,328 @@
|
||||
#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[];
|
||||
};
|
||||
|
||||
void 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)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
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, char *domain, int domain_len,
|
||||
char *cert_file, int cert_file_len, char *key_file, int key_file_len)
|
||||
{
|
||||
return secure_context_add_cert(&pool->sec, domain, domain_len, cert_file, cert_file_len, key_file, key_file_len);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#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;
|
||||
|
||||
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, char *domain, int domain_len, char *cert_file, int cert_file_len, char *key_file, int key_file_len);
|
||||
|
||||
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);
|
||||
|
||||
#endif // SOCKET_POOL_INCLUDED
|
||||
@@ -0,0 +1,88 @@
|
||||
#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 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 BAD_SOCKET;
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#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 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
|
||||
Reference in New Issue
Block a user