Refactoring for an improved event loop
This commit is contained in:
+162
-209
@@ -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
|
||||
@@ -31,14 +31,17 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
ClientConnectionState state;
|
||||
uint16_t gen;
|
||||
Socket socket;
|
||||
HTTP_Engine engine;
|
||||
bool trace;
|
||||
uint16_t gen;
|
||||
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)
|
||||
{
|
||||
HTTP_ASSERT(conn->state == CLIENT_CONNECTION_WAIT);
|
||||
|
||||
socket_update(&conn->socket);
|
||||
|
||||
while (socket_state(&conn->socket) == SOCKET_STATE_ESTABLISHED_READY) {
|
||||
|
||||
HTTP_EngineState engine_state;
|
||||
|
||||
engine_state = http_engine_state(&conn->engine);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) {
|
||||
int len;
|
||||
char *buf;
|
||||
buf = http_engine_recvbuf(&conn->engine, &len);
|
||||
if (buf) {
|
||||
int ret = socket_read(&conn->socket, buf, len);
|
||||
if (conn->trace)
|
||||
print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret });
|
||||
http_engine_recvack(&conn->engine, ret);
|
||||
}
|
||||
} else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) {
|
||||
int len;
|
||||
char *buf;
|
||||
buf = http_engine_sendbuf(&conn->engine, &len);
|
||||
if (buf) {
|
||||
int ret = socket_write(&conn->socket, buf, len);
|
||||
if (conn->trace)
|
||||
print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret });
|
||||
http_engine_sendack(&conn->engine, ret);
|
||||
}
|
||||
}
|
||||
|
||||
engine_state = http_engine_state(&conn->engine);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
|
||||
engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
|
||||
socket_close(&conn->socket);
|
||||
}
|
||||
|
||||
if (socket_state(&conn->socket) == SOCKET_STATE_DIED)
|
||||
conn->state = CLIENT_CONNECTION_DONE;
|
||||
}
|
||||
|
||||
int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle)
|
||||
int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data)
|
||||
{
|
||||
while (client->ready_count == 0) {
|
||||
|
||||
int num_polled = 0;
|
||||
int indices[CLIENT_MAX_CONNS];
|
||||
struct pollfd polled[CLIENT_MAX_CONNS];
|
||||
SocketEvent event = socket_pool_wait(client->socket_pool);
|
||||
switch (event.type) {
|
||||
|
||||
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)
|
||||
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) {
|
||||
case SOCKET_EVENT_DIED:
|
||||
{
|
||||
ClientConnection *conn = event.user_data;
|
||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
||||
client->ready[tail] = connidx;
|
||||
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->eng);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
engine_state = http_engine_state(&conn->eng);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
|
||||
engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
|
||||
socket_pool_close(client->socket_pool, conn->sock);
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_EVENT_ERROR:
|
||||
return -1;
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user