diff --git a/Makefile b/Makefile index 401858f..f27efb5 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,9 @@ CFILES = $(shell find src -name '*.c') HFILES = $(shell find src -name '*.h') OFILES = $(CFILES:.c=.o) +WEB_CFILES = $(shell find web/src web/3p -name '*.c') +WEB_HFILES = $(shell find web/src web/3p -name '*.h') + .PHONY: all clean coverage coverage-report coverage-html all: toastyfs$(EXT) toastyfs_web$(EXT) toastyfs_random_test$(EXT) example_async_api$(EXT) example_blocking_api$(EXT) libtoastyfs.a @@ -42,8 +45,8 @@ example_async_api$(EXT): libtoastyfs.a examples/async_api.c example_blocking_api$(EXT): libtoastyfs.a examples/blocking_api.c gcc -o $@ examples/blocking_api.c $(CFLAGS) -ltoastyfs $(LFLAGS) -Iinc -L. -toastyfs_web$(EXT): libtoastyfs.a web/main.c web/chttp.c web/chttp.h - gcc -o $@ web/main.c web/chttp.c $(CFLAGS) -ltoastyfs $(LFLAGS) -Iinc -L. +toastyfs_web$(EXT): libtoastyfs.a $(WEB_CFILES) $(WEB_HFILES) + gcc -o $@ $(WEB_CFILES) $(CFLAGS) -ltoastyfs $(LFLAGS) -Iinc -Iweb/3p -L. %.o: %.c $(HFILES) gcc -c -o $@ $< $(CFLAGS) -Iinc diff --git a/src/system.c b/src/system.c index d8c26f3..d18fd7f 100644 --- a/src/system.c +++ b/src/system.c @@ -1,3 +1,4 @@ +#include "basic.h" #include "tcp.h" #ifdef BUILD_TEST @@ -627,6 +628,9 @@ static int setup_poll_array(void **contexts, struct pollfd *polled) break; } break; + + default: + UNREACHABLE; } revents &= desc->events; @@ -1101,6 +1105,9 @@ int mock_getsockopt(SOCKET fd, int level, int optname, void *optval, socklen_t * break; } break; + + default: + UNREACHABLE; } *(int*)optval = val; *optlen = sizeof(int); diff --git a/web/chttp.c b/web/3p/chttp.c similarity index 100% rename from web/chttp.c rename to web/3p/chttp.c diff --git a/web/chttp.h b/web/3p/chttp.h similarity index 100% rename from web/chttp.h rename to web/3p/chttp.h diff --git a/web/main.c b/web/main.c deleted file mode 100644 index 44471b5..0000000 --- a/web/main.c +++ /dev/null @@ -1,435 +0,0 @@ -#include - -#include "chttp.h" - -#ifdef _WIN32 -#define POLL WSAPoll -#else -#define POLL poll -#endif - -#define UNREACHABLE __builtin_trap() - -#define MAX_PROXIED_OPERATIONS (1<<10) - -typedef enum { - PROXIED_OPERATION_FREE, - PROXIED_OPERATION_CREATE_DIR, - PROXIED_OPERATION_CREATE_FILE, - PROXIED_OPERATION_DELETE, - PROXIED_OPERATION_READ_DIR, - PROXIED_OPERATION_READ_FILE, - PROXIED_OPERATION_WRITE, -} ProxiedOperationType; - -typedef struct { - ProxiedOperationType type; - - // Don't write the content to the response - // when reading a file or directory. - bool head_only; - - // Offset of the read/write - int offset; - - // Length of the read/write - int length; - - // Number of bytes read/written - int transferred; - - HTTP_Request *request; // TODO: is it okay to store this pointer? - HTTP_ResponseBuilder builder; - - ToastyHandle handle; - -} ProxiedOperation; - -static int find_unused_struct(ProxiedOperation *arr, int num) -{ - if (num == MAX_PROXIED_OPERATIONS) - return -1; - int i = 0; - while (arr[i].type != PROXIED_OPERATION_FREE) { - i++; - assert(i < MAX_PROXIED_OPERATIONS); - } - return i; -} - -int main(int argc, char **argv) -{ - ToastyString upstream_addr = TOASTY_STR("127.0.0.1"); - uint16_t upstream_port = 9000; - - HTTP_String local_addr = HTTP_STR("127.0.0.1"); - uint16_t local_port = 8080; - - bool reuse_addr = false; - bool trace_bytes = false; - - for (int i = 1; i < argc; i++) { - - if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { - printf("TODO: print help\n"); - return 0; - } - - if (!strcmp(argv[i], "--upstream-addr")) { - - i++; - if (i == argc) { - fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); - return -1; - } - upstream_addr = (ToastyString) { argv[i], strlen(argv[i]) }; - - } else if (!strcmp(argv[i], "--upstream-port")) { - - i++; - if (i == argc) { - fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); - return -1; - } - int tmp = atoi(argv[i]); - if (tmp < 1 || tmp > UINT16_MAX) { - fprintf(stderr, "Error: Invalid port %s\n", argv[i]); - return -1; - } - upstream_port = (uint16_t) tmp; - - } else if (!strcmp(argv[i], "--local-addr")) { - - i++; - if (i == argc) { - fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); - return -1; - } - local_addr = (HTTP_String) { argv[i], strlen(argv[i]) }; - - } else if (!strcmp(argv[i], "--local-port")) { - - i++; - if (i == argc) { - fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); - return -1; - } - int tmp = atoi(argv[i]); - if (tmp < 1 || tmp > UINT16_MAX) { - fprintf(stderr, "Error: Invalid port %s\n", argv[i]); - return -1; - } - local_port = (uint16_t) tmp; - } else if(!strcmp(argv[i], "--reuse-addr")) { - reuse_addr = true; - } else if(!strcmp(argv[i], "--trace-bytes")) { - trace_bytes = true; - } else { - fprintf(stderr, "Error: Invalid option %s\n", argv[i]); - return -1; - } - } - - ToastyFS *toasty = toasty_connect(upstream_addr, upstream_port); - if (toasty == NULL) { - printf("toasty_connect error\n"); - return -1; - } - - HTTP_Server server; - if (http_server_init(&server) < 0) { - printf("http_server_init error\n"); - return -1; - } - - http_server_set_reuse_addr(&server, reuse_addr); - http_server_set_trace_bytes(&server, trace_bytes); - - if (http_server_listen_tcp(&server, local_addr, local_port) < 0) { - printf("http_server_listen_tcp error\n"); - return -1; - } - - int num_proxied = 0; - ProxiedOperation proxied[MAX_PROXIED_OPERATIONS]; - - for (int i = 0; i < MAX_PROXIED_OPERATIONS; i++) - proxied[i].type = PROXIED_OPERATION_FREE; - - for (;;) { - - #define POLL_CAPACITY (HTTP_SERVER_POLL_CAPACITY + TOASTY_POLL_CAPACITY) - - EventRegister reg; - void *ptrs[POLL_CAPACITY]; - struct pollfd polled[POLL_CAPACITY]; - - void **http_ptrs = ptrs; - struct pollfd *http_polled = polled; - - reg = (EventRegister) { ptrs, polled, 0 }; - http_server_register_events(&server, ®); - int num_http_polled = reg.num_polled; - - void **toasty_ptrs = ptrs + num_http_polled; - struct pollfd *toasty_polled = polled + num_http_polled; - int num_toasty_polled = toasty_process_events(toasty, toasty_ptrs, toasty_polled, 0); - if (num_toasty_polled < 0) - return -1; - - int num_polled = num_http_polled + num_toasty_polled; - if (num_http_polled != 0 && num_toasty_polled != 0) - POLL(polled, num_polled, -1); - - // First, process toasty events so that we free space - // for incoming requests - - if (toasty_process_events(toasty, toasty_ptrs, toasty_polled, num_toasty_polled) < 0) - return -1; - - for (;;) { - - ToastyResult result; - int ret = toasty_get_result(toasty, TOASTY_INVALID, &result); - if (ret == 1) break; // No completion - if (ret < 0) return -1; // Error - - // Completed - assert(ret == 0); - - int i = (ProxiedOperation*) result.user - proxied; - assert(i > -1 && i < MAX_PROXIED_OPERATIONS); - - switch (proxied[i].type) { - case PROXIED_OPERATION_CREATE_DIR: - case PROXIED_OPERATION_CREATE_FILE: - { - if (result.type == TOASTY_RESULT_CREATE_SUCCESS) { - http_response_builder_status(proxied[i].builder, 201); // Created - http_response_builder_send(proxied[i].builder); - } else { - // TODO: Should differentiate between error conditions - http_response_builder_status(proxied[i].builder, 500); // Internal Server Error - http_response_builder_send(proxied[i].builder); - } - proxied[i].type = PROXIED_OPERATION_FREE; - num_proxied--; - } - break; - case PROXIED_OPERATION_DELETE: - { - if (result.type == TOASTY_RESULT_DELETE_SUCCESS) { - http_response_builder_status(proxied[i].builder, 204); // No Content - http_response_builder_send(proxied[i].builder); - } else { - // TODO: Should differentiate between error conditions - http_response_builder_status(proxied[i].builder, 500); // Internal Server Error - http_response_builder_send(proxied[i].builder); - } - proxied[i].type = PROXIED_OPERATION_FREE; - num_proxied--; - } - break; - case PROXIED_OPERATION_READ_DIR: - { - if (result.type == TOASTY_RESULT_LIST_SUCCESS) { - http_response_builder_status(proxied[i].builder, 200); - for (int i = 0; i < result.listing.count; i++) - http_response_builder_body(proxied[i].builder, (HTTP_String) { - result.listing.items[i].name, strlen(result.listing.items[i].name), - }); - http_response_builder_send(proxied[i].builder); - toasty_free_listing(&result.listing); - } else { - // TODO: Should differentiate between error conditions - http_response_builder_status(proxied[i].builder, 500); // Internal Server Error - http_response_builder_send(proxied[i].builder); - } - proxied[i].type = PROXIED_OPERATION_FREE; - num_proxied--; - } - break; - case PROXIED_OPERATION_READ_FILE: - { - bool again = false; - if (result.type != TOASTY_RESULT_READ_SUCCESS) { - http_response_builder_body_ack(proxied[i].builder, 0); - http_response_builder_status(proxied[i].builder, 500); - http_response_builder_send(proxied[i].builder); - } else { - - // First, ACK the byte we just read, even if it's - // just 0 bytes (every bodybuf must by paired with - // a bodyack). - proxied[i].transferred += result.bytes_read; - int ack = proxied[i].head_only ? 0 : result.bytes_read; - http_response_builder_body_ack(proxied[i].builder, ack); - - // If we didn't reach the end of the file, start - // a new read. - if (result.bytes_read > 0) { - - // Make sure there is some free space in the buffer - int mincap = 1<<10; // TODO: Choose based on overall file size - http_response_builder_body_cap(proxied[i].builder, mincap); - - // Get the location of that buffer - int cap; - char *dst = http_response_builder_body_buf(proxied[i].builder, &cap); - if (dst == NULL) { - assert(0); // TODO - } - - ToastyString path = { - proxied[i].request->url.path.ptr, - proxied[i].request->url.path.len, - }; - proxied[i].handle = toasty_begin_read(toasty, path, proxied[i].transferred, dst, cap, TOASTY_VERSION_TAG_EMPTY); - if (proxied[i].handle == TOASTY_INVALID) { - assert(0); // TODO - } - - again = true; - } - } - - if (!again) { - proxied[i].type = PROXIED_OPERATION_FREE; - num_proxied--; - } - } - break; - case PROXIED_OPERATION_WRITE: - { - if (result.type == TOASTY_RESULT_WRITE_SUCCESS) { - http_response_builder_status(proxied[i].builder, 201); // Created - http_response_builder_send(proxied[i].builder); - } else { - // TODO: Should differentiate between error conditions - http_response_builder_status(proxied[i].builder, 500); // Internal Server Error - http_response_builder_send(proxied[i].builder); - } - proxied[i].type = PROXIED_OPERATION_FREE; - num_proxied--; - } - break; - default: - UNREACHABLE; - break; - } - } - - reg = (EventRegister) { http_ptrs, http_polled, num_http_polled }; - http_server_process_events(&server, reg); - - for (;;) { - HTTP_Request *request; - HTTP_ResponseBuilder builder; - if (!http_server_next_request(&server, &request, &builder)) - break; - - switch (request->method) { - case HTTP_METHOD_GET: - http_response_builder_status(builder, 501); // Not Implemented - http_response_builder_send(builder); - break; - case HTTP_METHOD_HEAD: - http_response_builder_status(builder, 501); // Not Implemented - http_response_builder_send(builder); - break; - case HTTP_METHOD_PUT: - { - int i = find_unused_struct(proxied, num_proxied); - if (i < 0) { - http_response_builder_status(builder, 503); // Service Unavailable - http_response_builder_send(builder); - break; - } - - ToastyVersionTag vtag; - { - int i = http_find_header(request->headers, request->num_headers, HTTP_STR("ETag")); - if (i < 0) - vtag = TOASTY_VERSION_TAG_EMPTY; - else { - // TODO: parse ETag and store its value into vtag - vtag = -1; - } - } - - ToastyString path = { - request->url.path.ptr, - request->url.path.len, - }; - ToastyHandle handle = toasty_begin_write(toasty, path, 0, request->body.ptr, request->body.len, vtag); - if (handle == TOASTY_INVALID) { - http_response_builder_status(builder, 500); // Internal Server Error - http_response_builder_send(builder); - break; - } - - toasty_set_user(toasty, handle, &proxied[num_proxied]); - proxied[num_proxied].type = PROXIED_OPERATION_WRITE; - proxied[num_proxied].request = request; - proxied[num_proxied].builder = builder; - proxied[num_proxied].handle = handle; - num_proxied++; - } - break; - case HTTP_METHOD_DELETE: - { - int i = find_unused_struct(proxied, num_proxied); - if (i < 0) { - http_response_builder_status(builder, 503); // Service Unavailable - http_response_builder_send(builder); - break; - } - - ToastyVersionTag vtag; - { - int i = http_find_header(request->headers, request->num_headers, HTTP_STR("ETag")); - if (i < 0) - vtag = TOASTY_VERSION_TAG_EMPTY; - else { - // TODO: parse ETag and store its value into vtag - vtag = -1; - } - } - - ToastyString path = { - request->url.path.ptr, - request->url.path.len, - }; - ToastyHandle handle = toasty_begin_delete(toasty, path, vtag); - if (handle == TOASTY_INVALID) { - http_response_builder_status(builder, 500); // Internal Server Error - http_response_builder_send(builder); - break; - } - - toasty_set_user(toasty, handle, &proxied[num_proxied]); - proxied[num_proxied].type = PROXIED_OPERATION_DELETE; - proxied[num_proxied].request = request; - proxied[num_proxied].builder = builder; - proxied[num_proxied].handle = handle; - num_proxied++; - } - break; - case HTTP_METHOD_OPTIONS: - http_response_builder_status(builder, 200); // OK - http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, DELETE, OPTIONS")); - http_response_builder_send(builder); - break; - default: - http_response_builder_status(builder, 405); // Method not allowed - http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, DELETE, OPTIONS")); - http_response_builder_send(builder); - break; - } - } - } - - http_server_free(&server); - toasty_disconnect(toasty); - return 0; -} diff --git a/web/src/config.c b/web/src/config.c new file mode 100644 index 0000000..5db1bc8 --- /dev/null +++ b/web/src/config.c @@ -0,0 +1,76 @@ +#include "config.h" + +void parse_config_or_exit(ProxyConfig *config, + int argc, char **argv) +{ + config->upstream_addr = TOASTY_STR("127.0.0.1"); + config->upstream_port = 9000; + + config->local_addr = HTTP_STR("127.0.0.1"); + config->local_port = 8080; + + config->reuse_addr = false; + config->trace_bytes = false; + + for (int i = 1; i < argc; i++) { + + if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { + printf("TODO: print help\n"); + exit(0); + } + + if (!strcmp(argv[i], "--upstream-addr")) { + + i++; + if (i == argc) { + fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); + exit(-1); + } + config->upstream_addr = (ToastyString) { argv[i], strlen(argv[i]) }; + + } else if (!strcmp(argv[i], "--upstream-port")) { + + i++; + if (i == argc) { + fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); + exit(-1); + } + int tmp = atoi(argv[i]); + if (tmp < 1 || tmp > UINT16_MAX) { + fprintf(stderr, "Error: Invalid port %s\n", argv[i]); + exit(-1); + } + config->upstream_port = (uint16_t) tmp; + + } else if (!strcmp(argv[i], "--local-addr")) { + + i++; + if (i == argc) { + fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); + exit(-1); + } + config->local_addr = (HTTP_String) { argv[i], strlen(argv[i]) }; + + } else if (!strcmp(argv[i], "--local-port")) { + + i++; + if (i == argc) { + fprintf(stderr, "Error: Missing value after %s\n", argv[i-1]); + exit(-1); + } + int tmp = atoi(argv[i]); + if (tmp < 1 || tmp > UINT16_MAX) { + fprintf(stderr, "Error: Invalid port %s\n", argv[i]); + exit(-1); + } + config->local_port = (uint16_t) tmp; + } else if(!strcmp(argv[i], "--reuse-addr")) { + config->reuse_addr = true; + } else if(!strcmp(argv[i], "--trace-bytes")) { + config->trace_bytes = true; + } else { + fprintf(stderr, "Error: Invalid option %s\n", argv[i]); + exit(-1); + } + } +} diff --git a/web/src/config.h b/web/src/config.h new file mode 100644 index 0000000..fb747ce --- /dev/null +++ b/web/src/config.h @@ -0,0 +1,21 @@ +#ifndef CONFIG_INCLUDED +#define CONFIG_INCLUDED + +#include +#include + +typedef struct { + ToastyString upstream_addr; + uint16_t upstream_port; + + HTTP_String local_addr; + uint16_t local_port; + + bool reuse_addr; + bool trace_bytes; +} ProxyConfig; + +void parse_config_or_exit(ProxyConfig *config, + int argc, char **argv); + +#endif // CONFIG_INCLUDED diff --git a/web/src/event_loop.c b/web/src/event_loop.c new file mode 100644 index 0000000..fecd284 --- /dev/null +++ b/web/src/event_loop.c @@ -0,0 +1,84 @@ +#include "event_loop.h" + +#ifdef _WIN32 +#define POLL WSAPoll +#else +#define POLL poll +#endif + +#define POLL_CAPACITY (HTTP_SERVER_POLL_CAPACITY + TOASTY_POLL_CAPACITY) + +void event_loop_init(EventLoop *loop, HTTP_Server *server, + ToastyFS *backend) +{ + loop->state = EVENT_LOOP_PROCESSING_COMPLETIONS; + loop->server = server; + loop->backend = backend; +} + +void event_loop_free(EventLoop *loop) +{ + (void) loop; +} + +int event_loop_wait(EventLoop *loop, Event *event) +{ + for (;;) { + switch (loop->state) { + + int ret; + case EVENT_LOOP_PROCESSING_COMPLETIONS: + + ret = toasty_get_result(loop->backend, TOASTY_INVALID, &event->completion); + if (ret < 0) + return -1; // Error + + if (ret == 0) { + // Completed + event->type = EVENT_TYPE_COMPLETION; + return 0; + } + + // fallthrough + case EVENT_LOOP_PROCESSING_REQUESTS: + + if (http_server_next_request(loop->server, &event->request, &event->builder)) { + // Completed + event->type = EVENT_TYPE_REQUEST; + return 0; + } + + // fallthrough + } + + EventRegister reg; + void *ptrs[POLL_CAPACITY]; + struct pollfd polled[POLL_CAPACITY]; + + void **http_ptrs = ptrs; + struct pollfd *http_polled = polled; + + reg = (EventRegister) { ptrs, polled, 0 }; + http_server_register_events(loop->server, ®); + int num_http_polled = reg.num_polled; + + void **toasty_ptrs = ptrs + num_http_polled; + struct pollfd *toasty_polled = polled + num_http_polled; + int num_toasty_polled = toasty_process_events(loop->backend, toasty_ptrs, toasty_polled, 0); + if (num_toasty_polled < 0) + return -1; + + int num_polled = num_http_polled + num_toasty_polled; + if (num_http_polled != 0 && num_toasty_polled != 0) + POLL(polled, num_polled, -1); + + if (toasty_process_events(loop->backend, toasty_ptrs, toasty_polled, num_toasty_polled) < 0) + return -1; + + reg = (EventRegister) { http_ptrs, http_polled, num_http_polled }; + http_server_process_events(loop->server, reg); + } + + // Unreachable + return -1; +} diff --git a/web/src/event_loop.h b/web/src/event_loop.h new file mode 100644 index 0000000..ac7814d --- /dev/null +++ b/web/src/event_loop.h @@ -0,0 +1,42 @@ +#ifndef EVENT_LOOP_INCLUDED +#define EVENT_LOOP_INCLUDED + +#include +#include "chttp.h" + +typedef enum { + EVENT_TYPE_REQUEST, + EVENT_TYPE_COMPLETION, +} EventType; + +typedef struct { + EventType type; + + // Completion + ToastyResult completion; + + // Request + HTTP_Request *request; + HTTP_ResponseBuilder builder; +} Event; + + +typedef enum { + EVENT_LOOP_PROCESSING_REQUESTS, + EVENT_LOOP_PROCESSING_COMPLETIONS, +} EventLoopState; + +typedef struct { + EventLoopState state; + HTTP_Server *server; + ToastyFS *backend; +} EventLoop; + +void event_loop_init(EventLoop *loop, HTTP_Server *server, + ToastyFS *backend); + +void event_loop_free(EventLoop *loop); + +int event_loop_wait(EventLoop *loop, Event *event); + +#endif // EVENT_LOOP_INCLUDED diff --git a/web/src/main.c b/web/src/main.c new file mode 100644 index 0000000..b14c39d --- /dev/null +++ b/web/src/main.c @@ -0,0 +1,51 @@ +#include "proxy.h" +#include "config.h" +#include "event_loop.h" + +int main(int argc, char **argv) +{ + ProxyConfig config; + parse_config_or_exit(&config, argc, argv); + + ToastyFS *backend = toasty_connect( + config.upstream_addr, + config.upstream_port); + if (backend == NULL) { + printf("toasty_connect error\n"); + return -1; + } + + HTTP_Server server; + if (http_server_init(&server) < 0) { + printf("http_server_init error\n"); + return -1; + } + http_server_set_reuse_addr(&server, config.reuse_addr); + http_server_set_trace_bytes(&server, config.trace_bytes); + if (http_server_listen_tcp(&server, config.local_addr, config.local_port) < 0) { + printf("http_server_listen_tcp error\n"); + return -1; + } + + EventLoop loop; + event_loop_init(&loop, &server, backend); + + ProxyState proxy; + proxy_init(&proxy, backend); + + for (Event event; !event_loop_wait(&loop, &event); ) { + + if (event.type == EVENT_TYPE_REQUEST) { + proxy_process_request(&proxy, event.request, event.builder); + } else { + assert(event.type == EVENT_TYPE_COMPLETION); + proxy_process_completion(&proxy, event.completion); + } + } + + proxy_free(&proxy); + event_loop_free(&loop); + http_server_free(&server); + toasty_disconnect(backend); + return 0; +} diff --git a/web/src/proxy.c b/web/src/proxy.c new file mode 100644 index 0000000..f0b27a0 --- /dev/null +++ b/web/src/proxy.c @@ -0,0 +1,124 @@ +#include "proxy.h" +#include "proxy_get.h" +#include "proxy_put.h" +#include "proxy_delete.h" + +void proxy_init(ProxyState *state, ToastyFS *backend) +{ + state->backend = backend; + state->num_operations = 0; + for (int i = 0; i < PROXY_CAPACITY; i++) + state->operations[i].type = PO_FREE; +} + +void proxy_free(ProxyState *state) +{ + for (int i = 0; i < PROXY_CAPACITY; i++) { + ProxyOperation *operation = &state->operations[i]; + if (operation->type != PO_FREE) { + http_response_builder_status(operation->builder, 500); + http_response_builder_send(operation->builder); + } + } +} + +static ProxyOperation *find_unused_operation(ProxyState *state) +{ + if (state->num_operations == PROXY_CAPACITY) + return NULL; + + ProxyOperation *operation = state->operations; + while (operation->type != PO_FREE) + operation++; + + return operation; +} + +void proxy_process_request(ProxyState *state, + HTTP_Request *request, HTTP_ResponseBuilder builder) +{ + ProxyOperation *operation = find_unused_operation(state); + + bool created = false; + switch (request->method) { + case HTTP_METHOD_GET: + if (operation == NULL) { + http_response_builder_status(builder, 503); // Service Unavailable + http_response_builder_send(builder); + } else { + created = process_request_get(state, operation, request, builder); + } + break; + case HTTP_METHOD_HEAD: + if (operation == NULL) { + http_response_builder_status(builder, 503); // Service Unavailable + http_response_builder_send(builder); + } else { + created = process_request_head(state, operation, request, builder); + } + break; + case HTTP_METHOD_PUT: + if (operation == NULL) { + http_response_builder_status(builder, 503); // Service Unavailable + http_response_builder_send(builder); + } else { + created = process_request_put(state, operation, request, builder); + } + break; + case HTTP_METHOD_DELETE: + if (operation == NULL) { + http_response_builder_status(builder, 503); // Service Unavailable + http_response_builder_send(builder); + } else { + created = process_request_delete(state, operation, request, builder); + } + break; + case HTTP_METHOD_OPTIONS: + http_response_builder_status(builder, 200); // OK + http_response_builder_header(builder, + HTTP_STR("Allow: GET, HEAD, PUT, DELETE, OPTIONS")); + http_response_builder_send(builder); + break; + default: + http_response_builder_status(builder, 405); // Method not allowed + http_response_builder_header(builder, + HTTP_STR("Allow: GET, HEAD, PUT, DELETE, OPTIONS")); + http_response_builder_send(builder); + break; + } + if (created) { + state->num_operations++; + } +} + +void proxy_process_completion(ProxyState *state, + ToastyResult completion) +{ + bool completed; + ProxyOperation *operation = completion.user; + switch (operation->type) { + case PO_CREATE_DIR: + completed = process_completion_create_dir(state, operation, completion); + break; + case PO_CREATE_FILE: + completed = process_completion_create_file(state, operation, completion); + break; + case PO_DELETE: + completed = process_completion_delete(state, operation, completion); + break; + case PO_READ_DIR: + completed = process_completion_read_dir(state, operation, completion); + break; + case PO_READ_FILE: + completed = process_completion_read_file(state, operation, completion); + break; + case PO_WRITE: + completed = process_completion_write(state, operation, completion); + break; + default: + } + if (completed) { + operation->type = PO_FREE; + state->num_operations--; + } +} diff --git a/web/src/proxy.h b/web/src/proxy.h new file mode 100644 index 0000000..a62408a --- /dev/null +++ b/web/src/proxy.h @@ -0,0 +1,44 @@ +#ifndef PROXY_INCLUDED +#define PROXY_INCLUDED + +#include +#include + +#define PROXY_CAPACITY (1<<10) + +typedef enum { + PO_FREE, + PO_CREATE_DIR, + PO_CREATE_FILE, + PO_DELETE, + PO_READ_DIR, + PO_READ_FILE, + PO_WRITE, +} ProxyOperationType; + +typedef struct { + ProxyOperationType type; + HTTP_Request* request; + HTTP_ResponseBuilder builder; + ToastyHandle handle; + + bool head_only; + int transferred; +} ProxyOperation; + +typedef struct { + ToastyFS *backend; + int num_operations; + ProxyOperation operations[PROXY_CAPACITY]; +} ProxyState; + +void proxy_init(ProxyState *state, ToastyFS *backend); +void proxy_free(ProxyState *state); + +void proxy_process_request(ProxyState *state, + HTTP_Request *request, HTTP_ResponseBuilder builder); + +void proxy_process_completion(ProxyState *state, + ToastyResult completion); + +#endif // PROXY_INCLUDED diff --git a/web/src/proxy_delete.c b/web/src/proxy_delete.c new file mode 100644 index 0000000..42bc473 --- /dev/null +++ b/web/src/proxy_delete.c @@ -0,0 +1,37 @@ +#include "proxy_delete.h" + +bool process_request_delete(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder) +{ + // TODO: Implement If-Match and If-None-Match headers + + HTTP_String path = request->url.path; + ToastyHandle handle = toasty_begin_delete(state->backend, + (ToastyString) { path.ptr, path.len }, TOASTY_VERSION_TAG_EMPTY); + if (handle == TOASTY_INVALID) { + http_response_builder_status(builder, 500); // Internal Server Error + http_response_builder_send(builder); + return false; + } + toasty_set_user(state->backend, handle, operation); + + operation->type = PO_DELETE; + operation->request = request; + operation->builder = builder; + operation->handle = handle; + return true; +} + +bool process_completion_delete(ProxyState *state, + ProxyOperation *operation, ToastyResult completion) +{ + if (completion.type == TOASTY_RESULT_DELETE_SUCCESS) { + http_response_builder_status(operation->builder, 204); // No Content + http_response_builder_send(operation->builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(operation->builder, 500); // Internal Server Error + http_response_builder_send(operation->builder); + } + return true; +} diff --git a/web/src/proxy_delete.h b/web/src/proxy_delete.h new file mode 100644 index 0000000..7f74132 --- /dev/null +++ b/web/src/proxy_delete.h @@ -0,0 +1,12 @@ +#ifndef PROXY_DELETE_INCLUDED +#define PROXY_DELETE_INCLUDED + +#include "proxy.h" + +bool process_request_delete(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder); + +bool process_completion_delete(ProxyState *state, + ProxyOperation *operation, ToastyResult completion); + +#endif // PROXY_DELETE_INCLUDED diff --git a/web/src/proxy_get.c b/web/src/proxy_get.c new file mode 100644 index 0000000..622756f --- /dev/null +++ b/web/src/proxy_get.c @@ -0,0 +1,121 @@ +#include "proxy_get.h" + +static bool path_refers_to_dir(ToastyString path) +{ + return path.len > 0 && path.ptr[path.len-1] == '/'; +} + +bool process_request_get(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder) +{ + assert(request->method == HTTP_METHOD_GET + || request->method == HTTP_METHOD_HEAD); + + http_response_builder_status(builder, 200); // OK + http_response_builder_body_cap(builder, 1<<10); // TODO: pick the prealloc better + + int cap; + char *dst = http_response_builder_body_buf(builder, &cap); + // TODO: check for NULL dst + + ToastyString path = { + request->url.path.ptr, + request->url.path.len + }; + + ToastyHandle handle; + if (path_refers_to_dir(path)) { + handle = toasty_begin_list(state->backend, path, + TOASTY_VERSION_TAG_EMPTY); + } else { + handle = toasty_begin_read(state->backend, path, + 0, dst, cap, TOASTY_VERSION_TAG_EMPTY); + } + if (handle == TOASTY_INVALID) { + http_response_builder_status(builder, 500); // Internal Server Error + http_response_builder_send(builder); + return false; + } + toasty_set_user(state->backend, handle, operation); + + operation->type = path_refers_to_dir(path) ? PO_READ_DIR : PO_READ_FILE; + operation->request = request; + operation->builder = builder; + operation->handle = handle; + operation->head_only = (request->method == HTTP_METHOD_HEAD); + operation->transferred = 0; + return true; +} + +bool process_request_head(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder) +{ + return process_request_get(state, operation, request, builder); +} + +bool process_completion_read_dir(ProxyState *state, + ProxyOperation *operation, ToastyResult completion) +{ + if (completion.type == TOASTY_RESULT_LIST_SUCCESS) { + http_response_builder_status(operation->builder, 200); + for (int i = 0; i < completion.listing.count; i++) + http_response_builder_body(operation->builder, (HTTP_String) { + completion.listing.items[i].name, strlen(completion.listing.items[i].name), + }); + http_response_builder_send(operation->builder); + toasty_free_listing(&completion.listing); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(operation->builder, 500); // Internal Server Error + http_response_builder_send(operation->builder); + } + return true; +} + +bool process_completion_read_file(ProxyState *state, + ProxyOperation *operation, ToastyResult completion) +{ + bool again = false; + if (completion.type != TOASTY_RESULT_READ_SUCCESS) { + http_response_builder_body_ack(operation->builder, 0); + http_response_builder_status(operation->builder, 500); + http_response_builder_send(operation->builder); + } else { + + // First, ACK the byte we just read, even if it's + // just 0 bytes (every bodybuf must by paired with + // a bodyack). + operation->transferred += completion.bytes_read; + int ack = operation->head_only ? 0 : completion.bytes_read; + http_response_builder_body_ack(operation->builder, ack); + + // If we didn't reach the end of the file, start + // a new read. + if (completion.bytes_read > 0) { + + // Make sure there is some free space in the buffer + int mincap = 1<<10; // TODO: Choose based on overall file size + http_response_builder_body_cap(operation->builder, mincap); + + // Get the location of that buffer + int cap; + char *dst = http_response_builder_body_buf(operation->builder, &cap); + if (dst == NULL) { + assert(0); // TODO + } + + ToastyString path = { + operation->request->url.path.ptr, + operation->request->url.path.len, + }; + operation->handle = toasty_begin_read(state->backend, path, operation->transferred, dst, cap, TOASTY_VERSION_TAG_EMPTY); + if (operation->handle == TOASTY_INVALID) { + assert(0); // TODO + } + + again = true; + } + } + + return !again; +} diff --git a/web/src/proxy_get.h b/web/src/proxy_get.h new file mode 100644 index 0000000..8c0d349 --- /dev/null +++ b/web/src/proxy_get.h @@ -0,0 +1,18 @@ +#ifndef PROXY_GET_INCLUDED +#define PROXY_GET_INCLUDED + +#include "proxy.h" + +bool process_request_get(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder); + +bool process_request_head(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder); + +bool process_completion_read_dir(ProxyState *state, + ProxyOperation *operation, ToastyResult completion); + +bool process_completion_read_file(ProxyState *state, + ProxyOperation *operation, ToastyResult completion); + +#endif // PROXY_GET_INCLUDED diff --git a/web/src/proxy_put.c b/web/src/proxy_put.c new file mode 100644 index 0000000..a479c9e --- /dev/null +++ b/web/src/proxy_put.c @@ -0,0 +1,66 @@ +#include "proxy_put.h" + +bool process_request_put(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder) +{ + ToastyString path = { + request->url.path.ptr, + request->url.path.len, + }; + ToastyHandle handle = toasty_begin_write(state->backend, path, 0, + request->body.ptr, request->body.len, TOASTY_VERSION_TAG_EMPTY); + if (handle == TOASTY_INVALID) { + http_response_builder_status(builder, 500); // Internal Server Error + http_response_builder_send(builder); + return false; + } + toasty_set_user(state->backend, handle, operation); + + operation->type = PO_WRITE; + operation->request = request; + operation->builder = builder; + operation->handle = handle; + return true; +} + +bool process_completion_create_dir(ProxyState *state, + ProxyOperation *operation, ToastyResult completion) +{ + if (completion.type == TOASTY_RESULT_CREATE_SUCCESS) { + http_response_builder_status(operation->builder, 201); // Created + http_response_builder_send(operation->builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(operation->builder, 500); // Internal Server Error + http_response_builder_send(operation->builder); + } + return true; +} + +bool process_completion_create_file(ProxyState *state, + ProxyOperation *operation, ToastyResult completion) +{ + if (completion.type == TOASTY_RESULT_CREATE_SUCCESS) { + http_response_builder_status(operation->builder, 201); // Created + http_response_builder_send(operation->builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(operation->builder, 500); // Internal Server Error + http_response_builder_send(operation->builder); + } + return true; +} + +bool process_completion_write(ProxyState *state, + ProxyOperation *operation, ToastyResult completion) +{ + if (completion.type == TOASTY_RESULT_WRITE_SUCCESS) { + http_response_builder_status(operation->builder, 201); // Created + http_response_builder_send(operation->builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(operation->builder, 500); // Internal Server Error + http_response_builder_send(operation->builder); + } + return true; +} diff --git a/web/src/proxy_put.h b/web/src/proxy_put.h new file mode 100644 index 0000000..0050fff --- /dev/null +++ b/web/src/proxy_put.h @@ -0,0 +1,18 @@ +#ifndef PROXY_PUT_INCLUDED +#define PROXY_PUT_INCLUDED + +#include "proxy.h" + +bool process_request_put(ProxyState *state, ProxyOperation *operation, + HTTP_Request *request, HTTP_ResponseBuilder builder); + +bool process_completion_create_dir(ProxyState *state, + ProxyOperation *operation, ToastyResult completion); + +bool process_completion_create_file(ProxyState *state, + ProxyOperation *operation, ToastyResult completion); + +bool process_completion_write(ProxyState *state, + ProxyOperation *operation, ToastyResult completion); + +#endif // PROXY_PUT_INCLUDED