feat(lnm): add most of the writing code
	
		
			
	
		
	
	
		
			
				
	
				ci/woodpecker/push/build Pipeline was successful
				
					Details
				
			
		
	
				
					
				
			
				
	
				ci/woodpecker/push/build Pipeline was successful
				
					Details
				
			
		
	
							parent
							
								
									77b62825a6
								
							
						
					
					
						commit
						e8bb089f5c
					
				| 
						 | 
				
			
			@ -17,6 +17,9 @@
 | 
			
		|||
    }                                                                          \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define LNM_MIN(x, y) ((x) < (y) ? (x) : (y))
 | 
			
		||||
#define LNM_MAX(x, y) ((x) > (y) ? (x) : (y))
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
  lnm_err_ok = 0,
 | 
			
		||||
  lnm_err_failed_alloc,
 | 
			
		||||
| 
						 | 
				
			
			@ -26,4 +29,12 @@ typedef enum {
 | 
			
		|||
  lnm_err_bad_regex
 | 
			
		||||
} lnm_err;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_loop lnm_http_loop;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_loop_conn lnm_http_conn;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_http_step lnm_http_step;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_http_route lnm_http_route;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,14 +7,6 @@
 | 
			
		|||
#include "lnm/http/req.h"
 | 
			
		||||
#include "lnm/http/res.h"
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_loop lnm_http_loop;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_loop_conn lnm_http_conn;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_http_step lnm_http_step;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_http_route lnm_http_route;
 | 
			
		||||
 | 
			
		||||
typedef enum lnm_http_step_err {
 | 
			
		||||
  lnm_http_step_err_done = 0,
 | 
			
		||||
  lnm_http_step_err_io_needed,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,17 @@
 | 
			
		|||
#define LNM_HTTP_RES
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
#include "lnm/common.h"
 | 
			
		||||
#include "lnm/http/consts.h"
 | 
			
		||||
 | 
			
		||||
typedef lnm_err (*data_fn)(size_t *written, char *buf, lnm_http_conn *conn,
 | 
			
		||||
                           size_t offset, size_t len);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Linked list elements used to store the response headers
 | 
			
		||||
 */
 | 
			
		||||
typedef struct lnm_http_res_header {
 | 
			
		||||
  struct {
 | 
			
		||||
    char *s;
 | 
			
		||||
| 
						 | 
				
			
			@ -19,12 +27,85 @@ typedef struct lnm_http_res_header {
 | 
			
		|||
  struct lnm_http_res_header *next;
 | 
			
		||||
} lnm_http_res_header;
 | 
			
		||||
 | 
			
		||||
typedef enum lnm_http_res_body_type {
 | 
			
		||||
  lnm_http_res_body_type_file = 0,
 | 
			
		||||
  lnm_http_res_body_type_buf,
 | 
			
		||||
  lnm_http_res_body_type_fn,
 | 
			
		||||
} lnm_http_res_body_type;
 | 
			
		||||
 | 
			
		||||
typedef struct lnm_http_res {
 | 
			
		||||
  lnm_http_status status;
 | 
			
		||||
  struct {
 | 
			
		||||
    lnm_http_res_header *head;
 | 
			
		||||
    lnm_http_res_header *current;
 | 
			
		||||
  } headers;
 | 
			
		||||
  struct {
 | 
			
		||||
    struct {
 | 
			
		||||
      char *buf;
 | 
			
		||||
      FILE *f;
 | 
			
		||||
      data_fn fn;
 | 
			
		||||
    } data;
 | 
			
		||||
    size_t len;
 | 
			
		||||
    bool owned;
 | 
			
		||||
    lnm_http_res_body_type type;
 | 
			
		||||
  } body;
 | 
			
		||||
  // General-purpose; meaning depends on the current state
 | 
			
		||||
  size_t written;
 | 
			
		||||
} lnm_http_res;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new header of a known type to the response
 | 
			
		||||
 *
 | 
			
		||||
 * @param type type of header
 | 
			
		||||
 * @param value null-terminated string containing the value of the header
 | 
			
		||||
 * @param value_owned whether to take ownership of the value pointer; if false,
 | 
			
		||||
 * free'ing the buffer is the caller's responsibility
 | 
			
		||||
 */
 | 
			
		||||
lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type,
 | 
			
		||||
                                char *value, bool value_owned);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new header of a known type to the response with a given value length.
 | 
			
		||||
 *
 | 
			
		||||
 * @param type type of header
 | 
			
		||||
 * @param value string of length `value_len` containing the value of the header
 | 
			
		||||
 * @param value_len length of value
 | 
			
		||||
 * @param value_owned whether to take ownership of the value pointer; if false,
 | 
			
		||||
 * free'ing the buffer is the caller's responsibility
 | 
			
		||||
 */
 | 
			
		||||
lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type,
 | 
			
		||||
                                    char *value, size_t value_len,
 | 
			
		||||
                                    bool value_owned);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the request body to the given file pointer.
 | 
			
		||||
 *
 | 
			
		||||
 * @param res response to modify
 | 
			
		||||
 * @param f file pointer to use as data
 | 
			
		||||
 * @param len expected length of the file
 | 
			
		||||
 * @param owned whether to take ownership of the file pointer
 | 
			
		||||
 */
 | 
			
		||||
void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len,
 | 
			
		||||
                                bool owned);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the request body to the given buffer.
 | 
			
		||||
 *
 | 
			
		||||
 * @param res response to modify
 | 
			
		||||
 * @param buf buffer to use as data
 | 
			
		||||
 * @param len length of the buffer
 | 
			
		||||
 * @param owned whether to take ownership of the file pointer
 | 
			
		||||
 */
 | 
			
		||||
void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len,
 | 
			
		||||
                               bool owned);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the request body to be read from the given data function.
 | 
			
		||||
 *
 | 
			
		||||
 * @param res response to modify
 | 
			
		||||
 * @param fn data reader function
 | 
			
		||||
 * @param len expected length of the response
 | 
			
		||||
 */
 | 
			
		||||
void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
#include <stdbool.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include "lnm/http/consts.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -116,25 +117,139 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) {
 | 
			
		|||
      ctx->state = lnm_http_loop_state_first_res;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  } while ((step != ctx->cur_step) && (ctx->cur_step != NULL));
 | 
			
		||||
  }
 | 
			
		||||
  // Loop until we either:
 | 
			
		||||
  // - reach the end of the chain of steps, indicated by NULL
 | 
			
		||||
  // - have a step that's waiting for I/O
 | 
			
		||||
  while ((ctx->cur_step != NULL) && (step != ctx->cur_step));
 | 
			
		||||
 | 
			
		||||
  if (ctx->cur_step == NULL) {
 | 
			
		||||
    ctx->state = lnm_http_loop_state_write_headers;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This function is intentionally written inefficiently for now, as it will most
 | 
			
		||||
// likely only have to run once for each response
 | 
			
		||||
void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) {
 | 
			
		||||
  lnm_http_loop_ctx *ctx = conn->ctx;
 | 
			
		||||
  lnm_http_res *res = &ctx->res;
 | 
			
		||||
 | 
			
		||||
  const char *response_type_name =
 | 
			
		||||
      lnm_http_status_names[res->status / 100 - 1][res->status % 100];
 | 
			
		||||
 | 
			
		||||
  // First we calculate the size of the start of the header
 | 
			
		||||
  size_t buf_size =
 | 
			
		||||
      snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name);
 | 
			
		||||
  char buf[buf_size + 1];
 | 
			
		||||
  sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name);
 | 
			
		||||
 | 
			
		||||
  size_t to_write =
 | 
			
		||||
      LNM_MIN(buf_size - res->written, LNM_LOOP_BUF_SIZE - conn->w.size);
 | 
			
		||||
  memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write);
 | 
			
		||||
 | 
			
		||||
  conn->w.size += to_write;
 | 
			
		||||
  res->written += to_write;
 | 
			
		||||
 | 
			
		||||
  if (res->written == buf_size) {
 | 
			
		||||
    res->written = 0;
 | 
			
		||||
    ctx->state = lnm_http_loop_state_write_headers;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void lnm_http_loop_process_write_headers(lnm_http_conn *conn) {
 | 
			
		||||
  lnm_http_loop_ctx *ctx = conn->ctx;
 | 
			
		||||
  lnm_http_res *res = &ctx->res;
 | 
			
		||||
 | 
			
		||||
  lnm_http_res_header *header = res->headers.current;
 | 
			
		||||
 | 
			
		||||
  // Loop as long as we can still write new data and have headers to write
 | 
			
		||||
  while ((conn->w.size < LNM_LOOP_BUF_SIZE) &&
 | 
			
		||||
         ((header = res->headers.current) != NULL)) {
 | 
			
		||||
    size_t buf_len = header->name.len + 2 + header->value.len + 1;
 | 
			
		||||
 | 
			
		||||
    // Here, we also constantly calculate the entire buffer as we assume each
 | 
			
		||||
    // header will be written in one go
 | 
			
		||||
    char buf[buf_len];
 | 
			
		||||
    memcpy(buf, header->name.s, header->name.len);
 | 
			
		||||
    memcpy(&buf[header->name.len + 2], header->value.s, header->value.len);
 | 
			
		||||
    buf[header->name.len] = ':';
 | 
			
		||||
    buf[header->name.len + 1] = ' ';
 | 
			
		||||
    buf[buf_len - 1] = '\n';
 | 
			
		||||
 | 
			
		||||
    size_t to_write =
 | 
			
		||||
        LNM_MIN(buf_len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size);
 | 
			
		||||
    memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write);
 | 
			
		||||
 | 
			
		||||
    conn->w.size += to_write;
 | 
			
		||||
    res->written += to_write;
 | 
			
		||||
 | 
			
		||||
    if (res->written == buf_len) {
 | 
			
		||||
      res->written = 0;
 | 
			
		||||
      res->headers.current = res->headers.current->next;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (res->headers.current == NULL) {
 | 
			
		||||
    ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body
 | 
			
		||||
                                       : lnm_http_loop_state_finish;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void lnm_http_loop_process_write_body(lnm_http_conn *conn) {
 | 
			
		||||
  lnm_http_loop_ctx *ctx = conn->ctx;
 | 
			
		||||
  lnm_http_res *res = &ctx->res;
 | 
			
		||||
 | 
			
		||||
  size_t to_write =
 | 
			
		||||
      LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size);
 | 
			
		||||
  size_t written;
 | 
			
		||||
 | 
			
		||||
  switch (res->body.type) {
 | 
			
		||||
  case lnm_http_res_body_type_buf:
 | 
			
		||||
    memcpy(&conn->w.buf[conn->w.size], &res->body.data.buf[res->written],
 | 
			
		||||
           to_write);
 | 
			
		||||
    written = to_write;
 | 
			
		||||
    break;
 | 
			
		||||
  case lnm_http_res_body_type_file:
 | 
			
		||||
    written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f);
 | 
			
		||||
 | 
			
		||||
    if ((written == 0) && (!ferror(res->body.data.f))) {
 | 
			
		||||
      ctx->state = lnm_http_loop_state_finish;
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
  case lnm_http_res_body_type_fn:
 | 
			
		||||
    if (res->body.data.fn(&written, &conn->w.buf[conn->w.size], conn,
 | 
			
		||||
                          res->written, to_write) != lnm_err_ok) {
 | 
			
		||||
      ctx->state = lnm_http_loop_state_finish;
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  conn->w.size += written;
 | 
			
		||||
  res->written += written;
 | 
			
		||||
 | 
			
		||||
  if (res->written == res->body.len) {
 | 
			
		||||
    ctx->state = lnm_http_loop_state_finish;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void lnm_http_loop_process_finish(lnm_http_conn *conn) {}
 | 
			
		||||
 | 
			
		||||
void (*process_fns[])(lnm_http_conn *conn) = {
 | 
			
		||||
    lnm_http_loop_process_parse_req,
 | 
			
		||||
    lnm_http_loop_process_route,
 | 
			
		||||
    lnm_http_loop_process_parse_headers,
 | 
			
		||||
    lnm_http_loop_process_steps,
 | 
			
		||||
    lnm_http_loop_process_write_status_line,
 | 
			
		||||
    lnm_http_loop_process_write_headers,
 | 
			
		||||
    lnm_http_loop_process_write_body,
 | 
			
		||||
    lnm_http_loop_process_finish,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void lnm_http_loop_process(lnm_http_conn *conn) {
 | 
			
		||||
  lnm_http_loop_ctx *ctx = conn->ctx;
 | 
			
		||||
 | 
			
		||||
  lnm_http_loop_state http_loop_state;
 | 
			
		||||
  lnm_loop_state loop_state;
 | 
			
		||||
  lnm_loop_state loop_state = conn->state;
 | 
			
		||||
 | 
			
		||||
  // We stop processing if:
 | 
			
		||||
  // - the event loop state has changed, as we need to switch to the other I/O
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +258,6 @@ void lnm_http_loop_process(lnm_http_conn *conn) {
 | 
			
		|||
  // it's waiting for I/O
 | 
			
		||||
  do {
 | 
			
		||||
    http_loop_state = ctx->state;
 | 
			
		||||
    loop_state = conn->state;
 | 
			
		||||
 | 
			
		||||
    process_fns[http_loop_state](conn);
 | 
			
		||||
  } while ((conn->state == loop_state) && (http_loop_state != ctx->state));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include "lnm/http/res.h"
 | 
			
		||||
 | 
			
		||||
lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type,
 | 
			
		||||
                                char *value, bool value_owned) {
 | 
			
		||||
  return lnm_http_res_add_header_len(res, type, value, strlen(value),
 | 
			
		||||
                                     value_owned);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type,
 | 
			
		||||
                                    char *value, size_t value_len,
 | 
			
		||||
                                    bool value_owned) {
 | 
			
		||||
  lnm_http_res_header *header = calloc(1, sizeof(lnm_http_res_header));
 | 
			
		||||
 | 
			
		||||
  if (header == NULL) {
 | 
			
		||||
    return lnm_err_failed_alloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  lnm_http_res_header **next_ptr = &res->headers.head;
 | 
			
		||||
 | 
			
		||||
  while ((*next_ptr) != NULL) {
 | 
			
		||||
    next_ptr = &(*next_ptr)->next;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *next_ptr = header;
 | 
			
		||||
 | 
			
		||||
  // Initialize the current pointer to the head of the linked list
 | 
			
		||||
  if (res->headers.current == NULL) {
 | 
			
		||||
    res->headers.current = header;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  header->name.s = (char *)lnm_http_header_names[type];
 | 
			
		||||
  header->name.len = strlen(lnm_http_header_names[type]);
 | 
			
		||||
  header->name.owned = false;
 | 
			
		||||
 | 
			
		||||
  header->value.s = value;
 | 
			
		||||
  header->value.len = value_len;
 | 
			
		||||
  header->value.owned = value_owned;
 | 
			
		||||
 | 
			
		||||
  return lnm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len,
 | 
			
		||||
                                bool owned) {
 | 
			
		||||
  res->body.data.f = f;
 | 
			
		||||
  res->body.len = len;
 | 
			
		||||
  res->body.owned = owned;
 | 
			
		||||
  res->body.type = lnm_http_res_body_type_file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len,
 | 
			
		||||
                               bool owned) {
 | 
			
		||||
  res->body.data.buf = buf;
 | 
			
		||||
  res->body.len = len;
 | 
			
		||||
  res->body.owned = owned;
 | 
			
		||||
  res->body.type = lnm_http_res_body_type_buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len) {
 | 
			
		||||
  res->body.data.fn = fn;
 | 
			
		||||
  res->body.len = len;
 | 
			
		||||
  res->body.type = lnm_http_res_body_type_fn;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue