feat: response bodies from char buffers; generated responses
							parent
							
								
									62348c14f5
								
							
						
					
					
						commit
						2fb3e2bb00
					
				|  | @ -53,6 +53,8 @@ typedef struct event_loop { | |||
|   void (*ctx_free)(void *ctx); | ||||
|   // Function that processes incoming data
 | ||||
|   bool (*handle_data)(event_loop_conn *conn); | ||||
|   // Function that writes outgoing data
 | ||||
|   void (*write_data)(event_loop_conn *conn); | ||||
| } event_loop; | ||||
| 
 | ||||
| /*
 | ||||
|  |  | |||
|  | @ -1,20 +1,13 @@ | |||
| #ifndef HTTP | ||||
| #define HTTP | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "picohttpparser.h" | ||||
| 
 | ||||
| #define HTTP_MAX_ALLOWED_HEADERS 8 | ||||
| #define HTTP_MAX_ALLOWED_HEADERS 16 | ||||
| 
 | ||||
| extern const char http_404[]; | ||||
| extern const size_t http_404_len; | ||||
| extern const char http_405[]; | ||||
| extern const size_t http_405_len; | ||||
| extern const char http_500[]; | ||||
| extern const size_t http_500_len; | ||||
| extern const char http_501[]; | ||||
| extern const size_t http_501_len; | ||||
| extern const char *http_response_type_names[5][32]; | ||||
| 
 | ||||
| typedef enum http_request_method { | ||||
|  | @ -120,9 +113,14 @@ typedef enum http_response_type { | |||
| 
 | ||||
| typedef struct http_response { | ||||
|   http_response_type type; | ||||
|   const char *head; | ||||
|   size_t head_len; | ||||
|   size_t head_written; | ||||
|   const char *body; | ||||
|   size_t body_len; | ||||
|   size_t body_written; | ||||
|   // If false, the body won't be freed
 | ||||
|   bool owns_body; | ||||
| } http_response; | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -39,7 +39,6 @@ http_loop_gctx *http_loop_gctx_init(); | |||
| typedef struct http_loop_ctx { | ||||
|   http_request req; | ||||
|   http_response res; | ||||
|   bool flush; | ||||
|   http_route *route; | ||||
|   size_t current_step; | ||||
|   http_loop_gctx *g; | ||||
|  | @ -62,12 +61,18 @@ void http_loop_ctx_free(http_loop_ctx *ctx); | |||
|  */ | ||||
| bool http_loop_handle_request(event_loop_conn *conn); | ||||
| 
 | ||||
| void http_loop_write_response(event_loop_conn *conn); | ||||
| 
 | ||||
| http_parse_error http_loop_parse_request(event_loop_conn *conn); | ||||
| 
 | ||||
| bool http_loop_route_request(event_loop_conn *conn); | ||||
| void http_loop_route_request(event_loop_conn *conn); | ||||
| 
 | ||||
| void http_loop_process_request(event_loop_conn *conn); | ||||
| 
 | ||||
| void http_loop_res_set_body(const char *body, size_t body_len); | ||||
| 
 | ||||
| void http_loop_res_set_type(http_response_type type); | ||||
| 
 | ||||
| void http_loop_write_standard_response(event_loop_conn *conn, | ||||
|                                        http_response_type type); | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,8 +4,8 @@ | |||
| 
 | ||||
| #include "event_loop.h" | ||||
| 
 | ||||
| void event_loop_conn_io_res(event_loop_conn *conn) { | ||||
|   while (1) { | ||||
| void event_loop_conn_io_res(event_loop *el, event_loop_conn *conn) { | ||||
|   do { | ||||
|     ssize_t res = 0; | ||||
|     size_t remain = conn->wbuf_size - conn->wbuf_sent; | ||||
| 
 | ||||
|  | @ -28,17 +28,15 @@ void event_loop_conn_io_res(event_loop_conn *conn) { | |||
| 
 | ||||
|     conn->wbuf_sent += (size_t)res; | ||||
| 
 | ||||
|     // After writing a response, we switch back to request mode to process a
 | ||||
|     // next request
 | ||||
|     // After writing the entire buffer, we run the write_data command to receive
 | ||||
|     // new data, or exit the loop
 | ||||
|     if (conn->wbuf_sent == conn->wbuf_size) { | ||||
|       conn->state = conn->close_after_write ? event_loop_conn_state_end | ||||
|                                             : event_loop_conn_state_req; | ||||
|       /* c->wbuf_sent = 0; */ | ||||
|       /* c->wbuf_size = 0; */ | ||||
|       conn->wbuf_sent = 0; | ||||
|       conn->wbuf_size = 0; | ||||
| 
 | ||||
|       return; | ||||
|       el->write_data(conn); | ||||
|     } | ||||
|   } | ||||
|   } while (conn->state == event_loop_conn_state_res); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -93,7 +91,7 @@ void event_loop_conn_io(event_loop *el, event_loop_conn *conn) { | |||
|     event_loop_conn_io_req(el, conn); | ||||
|     break; | ||||
|   case event_loop_conn_state_res: | ||||
|     event_loop_conn_io_res(conn); | ||||
|     event_loop_conn_io_res(el, conn); | ||||
|     break; | ||||
|   case event_loop_conn_state_end:; | ||||
|   } | ||||
|  |  | |||
|  | @ -0,0 +1,6 @@ | |||
| #include "http.h" | ||||
| 
 | ||||
| // Very important that this is in the same order as http_request_method
 | ||||
| const char *request_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; | ||||
| const size_t request_method_names_len = | ||||
|     sizeof(request_method_names) / sizeof(request_method_names[0]); | ||||
|  | @ -21,22 +21,22 @@ bool http_loop_handle_request(event_loop_conn *conn) { | |||
| 
 | ||||
|       return false; | ||||
|     } | ||||
|     // It's fun to respond with extremely specific error messages
 | ||||
|     // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501
 | ||||
|     else if (res == http_parse_error_unknown_method) { | ||||
|       http_loop_write_standard_response(conn, 501); | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     conn->rbuf_read += res; | ||||
| 
 | ||||
|     // If the routing fails, we exit the handler
 | ||||
|     if (!http_loop_route_request(conn)) { | ||||
|       return false; | ||||
|     // It's fun to respond with extremely specific error messages
 | ||||
|     // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501
 | ||||
|     if (res == http_parse_error_unknown_method) { | ||||
|       ctx->res.type = http_method_not_implemented; | ||||
|       conn->state = event_loop_conn_state_res; | ||||
|     } else { | ||||
|       http_loop_route_request(conn); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   http_loop_process_request(conn); | ||||
|   if (conn->state == event_loop_conn_state_req) { | ||||
|     http_loop_process_request(conn); | ||||
|   } | ||||
| 
 | ||||
|   // TODO in highly concurrent situations, it might actually be better to always
 | ||||
|   // return false here, as this allows cycling better through all connections
 | ||||
|  | @ -49,6 +49,7 @@ event_loop *http_loop_init(http_loop_gctx *gctx) { | |||
|   el->ctx_init = (void *(*)(void *))http_loop_ctx_init; | ||||
|   el->ctx_free = (void (*)(void *))http_loop_ctx_free; | ||||
|   el->handle_data = http_loop_handle_request; | ||||
|   el->write_data = http_loop_write_response; | ||||
|   el->gctx = gctx; | ||||
| 
 | ||||
|   return el; | ||||
|  |  | |||
|  | @ -1,21 +0,0 @@ | |||
| #include "http.h" | ||||
| 
 | ||||
| const char http_404[] = "HTTP/1.1 404 Not Found\n" | ||||
|                         "Content-Length: 0\n\n"; | ||||
| const size_t http_404_len = sizeof(http_404) - 1; | ||||
| 
 | ||||
| const char http_405[] = "HTTP/1.1 405 Method Not Allowed\n" | ||||
|                         "Content-Length: 0\n\n"; | ||||
| const size_t http_405_len = sizeof(http_405) - 1; | ||||
| 
 | ||||
| const char http_500[] = "HTTP/1.1 500 Internal Server Error\n" | ||||
|                         "Content-Length: 0\n\n"; | ||||
| const size_t http_500_len = sizeof(http_500) - 1; | ||||
| const char http_501[] = "HTTP/1.1 501 Not Implemented\n" | ||||
|                         "Content-Length: 0\n\n"; | ||||
| const size_t http_501_len = sizeof(http_501) - 1; | ||||
| 
 | ||||
| // Very important that this is in the same order as http_request_method
 | ||||
| const char *request_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; | ||||
| const size_t request_method_names_len = | ||||
|     sizeof(request_method_names) / sizeof(request_method_names[0]); | ||||
|  | @ -14,10 +14,30 @@ http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) { | |||
|   return ctx; | ||||
| } | ||||
| 
 | ||||
| void http_loop_ctx_free(http_loop_ctx *ctx) { free(ctx); } | ||||
| void http_loop_ctx_free(http_loop_ctx *ctx) { | ||||
|   http_loop_ctx_reset(ctx); | ||||
| 
 | ||||
|   free(ctx); | ||||
| } | ||||
| 
 | ||||
| void http_loop_ctx_reset(http_loop_ctx *ctx) { | ||||
|   ctx->route = NULL; | ||||
|   ctx->current_step = 0; | ||||
|   ctx->flush = false; | ||||
| 
 | ||||
|   if (ctx->res.head != NULL) { | ||||
|     free((void *)ctx->res.head); | ||||
|     ctx->res.head = NULL; | ||||
|   } | ||||
| 
 | ||||
|   if (ctx->res.owns_body && ctx->res.body != NULL) { | ||||
|     free((void *)ctx->res.body); | ||||
|   } | ||||
| 
 | ||||
|   ctx->res.body = NULL; | ||||
| 
 | ||||
|   ctx->res.head_len = 0; | ||||
|   ctx->res.head_written = 0; | ||||
|   ctx->res.body_len = 0; | ||||
|   ctx->res.body_written = 0; | ||||
|   ctx->res.owns_body = false; | ||||
| } | ||||
|  |  | |||
|  | @ -71,24 +71,7 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) { | |||
|   return http_parse_error_ok; | ||||
| } | ||||
| 
 | ||||
| void http_loop_process_request(event_loop_conn *conn) { | ||||
|   http_loop_ctx *ctx = conn->ctx; | ||||
| 
 | ||||
|   // We keep processing step functions as long as they don't need to wait for
 | ||||
|   // I/O
 | ||||
|   while ((ctx->route->steps[ctx->current_step] != NULL) && | ||||
|          ctx->route->steps[ctx->current_step](conn)) { | ||||
|     ctx->current_step++; | ||||
|   } | ||||
| 
 | ||||
|   // If we've reached the end of the list of step functions, we report the
 | ||||
|   // request as finished by clearing its route
 | ||||
|   if (ctx->route->steps[ctx->current_step] == NULL) { | ||||
|     http_loop_ctx_reset(ctx); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool http_loop_route_request(event_loop_conn *conn) { | ||||
| void http_loop_route_request(event_loop_conn *conn) { | ||||
|   http_loop_ctx *ctx = conn->ctx; | ||||
|   http_loop_gctx *gctx = ctx->g; | ||||
| 
 | ||||
|  | @ -104,7 +87,7 @@ bool http_loop_route_request(event_loop_conn *conn) { | |||
|     case http_route_literal: | ||||
|       if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) { | ||||
|         ctx->route = route; | ||||
|         return true; | ||||
|         return; | ||||
|       } | ||||
|     // TODO
 | ||||
|     case http_route_regex:; | ||||
|  | @ -112,7 +95,28 @@ bool http_loop_route_request(event_loop_conn *conn) { | |||
|   } | ||||
| 
 | ||||
|   // Fallthrough is to write a 404
 | ||||
|   http_loop_write_standard_response(conn, http_not_found); | ||||
| 
 | ||||
|   return false; | ||||
|   ctx->res.type = http_not_found; | ||||
|   conn->state = event_loop_conn_state_res; | ||||
| } | ||||
| 
 | ||||
| void http_loop_process_request(event_loop_conn *conn) { | ||||
|   http_loop_ctx *ctx = conn->ctx; | ||||
| 
 | ||||
|   // We keep processing step functions as long as they don't need to wait for
 | ||||
|   // I/O
 | ||||
|   while ((conn->state == event_loop_conn_state_req) && | ||||
|          (ctx->route->steps[ctx->current_step] != NULL) && | ||||
|          ctx->route->steps[ctx->current_step](conn)) { | ||||
|     ctx->current_step++; | ||||
|   } | ||||
| 
 | ||||
|   if (conn->state != event_loop_conn_state_req) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // If we've reached the end of the list of step functions, we report the
 | ||||
|   // request as finished by clearing its route
 | ||||
|   if (ctx->route->steps[ctx->current_step] == NULL) { | ||||
|     http_loop_ctx_reset(ctx); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| #include <stdio.h> | ||||
| 
 | ||||
| #include "http_loop.h" | ||||
| #include "log.h" | ||||
| 
 | ||||
| #define MIN(x, y) (((x) < (y)) ? (x) : (y)) | ||||
| 
 | ||||
| void http_loop_write_response(event_loop_conn *conn) { | ||||
|   http_response *res = &((http_loop_ctx *)conn->ctx)->res; | ||||
| 
 | ||||
|   // Create head response
 | ||||
|   if (res->head == NULL) { | ||||
|     const char *response_type_name = | ||||
|         http_response_type_names[res->type / 100 - 1][res->type % 100]; | ||||
| 
 | ||||
|     char *format = "HTTP/1.1 %i %s\n" | ||||
|                    "Content-Length: %lu\n\n"; | ||||
| 
 | ||||
|     // Running snprintf with size 0 prevents it from writing any bytes, while
 | ||||
|     // still letting it calculate how many bytes it would have written
 | ||||
|     int buf_size = | ||||
|         snprintf(NULL, 0, format, res->type, response_type_name, res->body_len); | ||||
|     char *buf = malloc(buf_size + 1); | ||||
|     sprintf(buf, format, res->type, response_type_name, res->body_len); | ||||
| 
 | ||||
|     res->head = buf; | ||||
|     res->head_len = buf_size; | ||||
|   } | ||||
| 
 | ||||
|   // The final iteration marks the end of the response, after which we reset the
 | ||||
|   // context so a next request can be processed
 | ||||
|   if (res->head_written == res->head_len && | ||||
|       res->body_written == res->body_len) { | ||||
|     http_loop_ctx_reset(conn->ctx); | ||||
|     conn->state = event_loop_conn_state_req; | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (res->head_written < res->head_len) { | ||||
|     size_t bytes_to_write = MIN(res->head_len - res->head_written, | ||||
|                                 EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); | ||||
|     memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written], | ||||
|            bytes_to_write); | ||||
| 
 | ||||
|     conn->wbuf_size += bytes_to_write; | ||||
|     res->head_written += bytes_to_write; | ||||
|   } | ||||
| 
 | ||||
|   if (res->body_written < res->body_len) { | ||||
|     size_t bytes_to_write = MIN(res->body_len - res->body_written, | ||||
|                                 EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); | ||||
|     memcpy(&conn->wbuf[conn->wbuf_size], &res->body[res->body_written], | ||||
|            bytes_to_write); | ||||
| 
 | ||||
|     conn->wbuf_size += bytes_to_write; | ||||
|     res->body_written += bytes_to_write; | ||||
|   } | ||||
| } | ||||
|  | @ -1,27 +0,0 @@ | |||
| #include "http_loop.h" | ||||
| #include "string.h" | ||||
| 
 | ||||
| void http_loop_write_standard_response(event_loop_conn *conn, | ||||
|                                        http_response_type type) { | ||||
|   const char *s; | ||||
|   size_t len; | ||||
| 
 | ||||
|   switch (type) { | ||||
|   case 404: | ||||
|     s = http_404; | ||||
|     len = http_404_len; | ||||
|     break; | ||||
|   case 405: | ||||
|     s = http_405; | ||||
|     len = http_405_len; | ||||
|   case 501: | ||||
|     s = http_501; | ||||
|     len = http_501_len; | ||||
|   } | ||||
| 
 | ||||
|   memcpy(conn->wbuf, s, len); | ||||
| 
 | ||||
|   conn->state = event_loop_conn_state_res; | ||||
|   conn->wbuf_size = len; | ||||
|   conn->wbuf_sent = 0; | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/main.c
								
								
								
								
							
							
						
						
									
										19
									
								
								src/main.c
								
								
								
								
							|  | @ -4,12 +4,23 @@ | |||
| #include "http_loop.h" | ||||
| #include "log.h" | ||||
| 
 | ||||
| bool http_write_404(event_loop_conn *conn) { | ||||
|   memcpy(conn->wbuf, http_405, http_405_len); | ||||
| const char index_page[] = | ||||
|     "<!DOCTYPE html>\n" | ||||
|     "<html>\n" | ||||
|     "  <body>\n" | ||||
|     "    <h1>r8r.be</h1>\n" | ||||
|     "    <p>This is the URL shortener and pastebin accompanying my site, <a " | ||||
|     "href=\"https://rustybever.be\">The Rusty Bever</a>.</p>\n" | ||||
|     "  </body>\n" | ||||
|     "</html>\n"; | ||||
| 
 | ||||
| bool http_write_404(event_loop_conn *conn) { | ||||
|   http_loop_ctx *ctx = conn->ctx; | ||||
|   ctx->res.type = 200; | ||||
|   ctx->res.body = index_page; | ||||
|   ctx->res.body_len = sizeof(index_page) - 1; | ||||
|   ctx->res.owns_body = false; | ||||
|   conn->state = event_loop_conn_state_res; | ||||
|   conn->wbuf_size = http_405_len; | ||||
|   conn->wbuf_sent = 0; | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue