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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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