Refactoring of toastyfs_web
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
-435
@@ -1,435 +0,0 @@
|
||||
#include <ToastyFS.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef CONFIG_INCLUDED
|
||||
#define CONFIG_INCLUDED
|
||||
|
||||
#include <chttp.h>
|
||||
#include <ToastyFS.h>
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef EVENT_LOOP_INCLUDED
|
||||
#define EVENT_LOOP_INCLUDED
|
||||
|
||||
#include <ToastyFS.h>
|
||||
#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
|
||||
@@ -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;
|
||||
}
|
||||
+124
@@ -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--;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef PROXY_INCLUDED
|
||||
#define PROXY_INCLUDED
|
||||
|
||||
#include <chttp.h>
|
||||
#include <ToastyFS.h>
|
||||
|
||||
#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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user