Draft of the REST server client using cHTTP
This commit is contained in:
+317
@@ -0,0 +1,317 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <chttp.h>
|
||||
#include <ToastyFS.h>
|
||||
|
||||
#define MAX_WAITING (1<<10)
|
||||
#define MAX_PENDING (1<<9)
|
||||
|
||||
typedef enum {
|
||||
OPERATION_FREE,
|
||||
OPERATION_CREATE_DIR,
|
||||
OPERATION_CREATE_FILE,
|
||||
OPERATION_DELETE,
|
||||
OPERATION_READ_DIR,
|
||||
OPERATION_READ_FILE,
|
||||
OPERATION_WRITE,
|
||||
} OperationType;
|
||||
|
||||
typedef struct {
|
||||
OperationType type;
|
||||
|
||||
// Number of bytes read/written
|
||||
int transferred;
|
||||
|
||||
HTTP_Request *request; // TODO: is it okay to store this pointer?
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
ToastyHandle handle;
|
||||
} Operation;
|
||||
|
||||
static int waiting_head;
|
||||
static int num_waiting;
|
||||
static int num_pending;
|
||||
static Operation waiting[MAX_WAITING];
|
||||
static Operation pending[MAX_PENDING];
|
||||
|
||||
void worker(Worker *w)
|
||||
{
|
||||
for (;;) {
|
||||
|
||||
ToastyResult result;
|
||||
int ret = toasty_wait_result(toasty, TOASTY_INVALID, &result, -1);
|
||||
// TODO: check return value
|
||||
|
||||
// First, process completed requests. This frees up
|
||||
// space for new ones.
|
||||
//
|
||||
// TODO: What if there was a WAKEUP request and no pending operations
|
||||
// are present?
|
||||
|
||||
int i = 0;
|
||||
while (pending[i].handle != result.handle) {
|
||||
i++;
|
||||
assert(i < MAX_PENDING_OPERATIONS);
|
||||
}
|
||||
|
||||
bool incomplete = false;
|
||||
switch (pending[i].type) {
|
||||
|
||||
int cap;
|
||||
int mincap;
|
||||
char *dst;
|
||||
ToastyString path;
|
||||
|
||||
case OPERATION_CREATE_DIR:
|
||||
case OPERATION_CREATE_FILE:
|
||||
if (result.type == TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
http_response_builder_status(pending[i].builder, 204); // TODO: What is the proper response to a CREATE request?
|
||||
http_response_builder_done(pending[i].builder);
|
||||
} else {
|
||||
http_response_builder_undo(pending[i].builder);
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERATION_DELETE:
|
||||
if (result.type == TOASTY_RESULT_DELETE_SUCCESS) {
|
||||
http_response_builder_status(pending[i].builder, 204); // TODO: What is the proper response to a DELETE request?
|
||||
http_response_builder_done(pending[i].builder);
|
||||
} else {
|
||||
http_response_builder_undo(pending[i].builder);
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERATION_READ_DIR:
|
||||
|
||||
// If the listing failed, abort the request
|
||||
if (result.type != TOASTY_RESULT_READ_SUCCESS) {
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
}
|
||||
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
for (int i = 0; i < result.listing.count; i++)
|
||||
http_response_builder_body(pending[i].builder, (HTTP_String) {
|
||||
result.listing.items[i].name,
|
||||
result.listing.items[i].name_len
|
||||
});
|
||||
http_response_builder_done(pending[i].builder);
|
||||
|
||||
toasty_free_listing(&result.listing);
|
||||
break;
|
||||
|
||||
case OPERATION_READ_FILE:
|
||||
|
||||
// If the read failed, abort the request
|
||||
if (result.type != TOASTY_RESULT_READ_SUCCESS) {
|
||||
http_response_builder_bodyack(pending[i].builder, 0);
|
||||
http_response_builder_undo(pending[i].builder);
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
}
|
||||
|
||||
// First, ACK the byte we just read, even if it's
|
||||
// just 0 bytes (every bodybuf must by paired with
|
||||
// a bodyack).
|
||||
http_response_builder_bodyack(pending[i].builder, result.count);
|
||||
pending[i].transferred += result.count;
|
||||
|
||||
// If we read 0 bytes, there is no more to read.
|
||||
if (result.count == 0) {
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
}
|
||||
|
||||
// There is more to read, so we need to start a
|
||||
// new read.
|
||||
|
||||
// Make sure there is some free space in the buffer
|
||||
mincap = 1<<10; // TODO: Choose based on overall file size
|
||||
http_response_builder_bodycap(pending[i].builder, mincap);
|
||||
|
||||
// Get the location of that buffer
|
||||
dst = http_response_builder_bodybuf(pending[i].builder, &cap);
|
||||
if (dst == NULL) {
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
}
|
||||
|
||||
// Begin the read. On error, abort.
|
||||
path = (ToastyString) { pending[i].request->path.ptr, pending[i].request.path.len };
|
||||
pending[i].handle = toasty_begin_read(toasty, path, off, dst, cap);
|
||||
if (pending[i].handle == TOASTY_INVALID) {
|
||||
http_response_builder_undo(pending[i].builder);
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
}
|
||||
|
||||
// Most of the time this switch will complete the pending
|
||||
// operation, but not for reads.
|
||||
incomplete = true;
|
||||
break;
|
||||
|
||||
case OPERATION_WRITE:
|
||||
if (pending[i].type != TOASTY_RESULT_WRITE_SUCCESS) {
|
||||
http_response_builder_undo(pending[i].builder);
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
}
|
||||
assert(pending[i].transferred == result.count);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0); // TODO
|
||||
}
|
||||
|
||||
if (!incomplete) {
|
||||
// Free the pending structure
|
||||
pending[i].type = OPERATION_FREE;
|
||||
num_pending--;
|
||||
}
|
||||
|
||||
// Now accept operations
|
||||
for (Operation req; num_pending < MAX_PENDING_OPERATIONS
|
||||
&& wait_queue_pop(&wait_queue, &req); ) {
|
||||
|
||||
int i = 0;
|
||||
while (pending[i].type != PENDING_OPERATION_FREE) {
|
||||
i++;
|
||||
assert(i < MAX_PENDING_OPERATIONS);
|
||||
}
|
||||
pending[i] = req;
|
||||
|
||||
ToastyString path = { pending[i].request->path.ptr, pending[i].request.path.len };
|
||||
ToastyString body = { pending[i].request->body.ptr, pending[i].request.body.len };
|
||||
switch (pending[i].type) {
|
||||
int cap;
|
||||
int mincap;
|
||||
char *dst;
|
||||
uint32_t chunk_size;
|
||||
case OPERATION_CREATE_DIR,
|
||||
pending[i].handle = toasty_begin_create_dir(toasty, path);
|
||||
break;
|
||||
case OPERATION_CREATE_FILE,
|
||||
chunk_size = 1<<10; // TODO: determine a better chunk size
|
||||
pending[i].handle = toasty_begin_create_file(toasty, path, chunk_size);
|
||||
break;
|
||||
case OPERATION_DELETE,
|
||||
pending[i].handle = toasty_begin_delete(toasty, path);
|
||||
break;
|
||||
case OPERATION_READ,
|
||||
http_response_builder_status(pending[i].builder, 200); // TODO: Is statis 200 correct?
|
||||
|
||||
mincap = 1<<10; // TODO: do something smart to choose this
|
||||
http_response_builder_bodycap(pending[i].builder, mincap);
|
||||
|
||||
dst = http_response_builder_bodybuf(pending[i].builder, &cap);
|
||||
if (dst == NULL)
|
||||
break;
|
||||
|
||||
pending[i].handle = toasty_begin_read(toasty, path, off, dst, cap);
|
||||
pending[i].transferred = 0;
|
||||
break;
|
||||
case OPERATION_WRITE:
|
||||
pending[i].handle = toasty_begin_write(toasty, body.ptr, body.len);
|
||||
pending[i].transferred = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pending[i].handle == TOASTY_INVALID) {
|
||||
http_response_builder_undo(pending[i].builder);
|
||||
http_response_builder_status(pending[i].builder, 500);
|
||||
http_response_builder_done(pending[i].builder);
|
||||
pending[i].type = OPERATION_FREE;
|
||||
}
|
||||
num_pending++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
HTTP_String addr = HTTP_STR("127.0.0.1");
|
||||
uint16_t port = 8080;
|
||||
|
||||
ToastyString backend_addr = TOASTY_STR("127.0.0.1");
|
||||
uint16_t backend_port = 9000;
|
||||
|
||||
HTTP_Server *server = http_server_init(addr, port);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
ToastyFS *toasty = toasty_connect(backend_addr, backend_port);
|
||||
if (toasty == NULL)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *request;
|
||||
HTTP_ResponseBuilder builder;
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ToastyString path = {
|
||||
req->url.path.ptr,
|
||||
req->url.path.len
|
||||
};
|
||||
|
||||
Operation op;
|
||||
op.type = OPERATION_FREE;
|
||||
|
||||
switch (req->method) {
|
||||
|
||||
case HTTP_METHOD_GET:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case HTTP_METHOD_HEAD:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case HTTP_METHOD_PUT:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case HTTP_METHOD_PATCH:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case HTTP_METHOD_DELETE:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case HTTP_METHOD_OPTIONS:
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS"));
|
||||
http_response_builder_done(builder);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_builder_status(builder, 200); // TODO: use the status code for invalid methods
|
||||
http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS"));
|
||||
http_response_builder_done(builder);
|
||||
break;
|
||||
}
|
||||
|
||||
if (op.type != OPERATION_FREE) {
|
||||
assert(0); // TODO: Append to the queue
|
||||
}
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user