133 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C
		
	
	
| #include <stdio.h>
 | |
| #include <sys/stat.h>
 | |
| 
 | |
| #include "http_loop.h"
 | |
| #include "log.h"
 | |
| 
 | |
| #define MIN(x, y) (((x) < (y)) ? (x) : (y))
 | |
| 
 | |
| static const char *http_response_format = "HTTP/1.1 %i %s\n"
 | |
|                                           "Server: lander/" LANDER_VERSION "\n"
 | |
|                                           "Content-Length: %lu\n";
 | |
| 
 | |
| /*
 | |
|  * This function precalculates the size of the total buffer required using
 | |
|  * snprintf. When this function is called with a buf size of 0, it never tries
 | |
|  * to write any data, but it does return the amount of bytes that would be
 | |
|  * written.
 | |
|  */
 | |
| void http_loop_init_header(http_response *res) {
 | |
|   if (res->type == 0) {
 | |
|     res->type = http_ok;
 | |
|   }
 | |
| 
 | |
|   const char *response_type_name =
 | |
|       http_response_type_names[res->type / 100 - 1][res->type % 100];
 | |
| 
 | |
|   // First we calculate the size of the start of the header
 | |
|   int buf_size = snprintf(NULL, 0, http_response_format, res->type,
 | |
|                           response_type_name, res->body_len);
 | |
| 
 | |
|   // We add each header's required size
 | |
|   for (size_t i = 0; i < res->header_count; i++) {
 | |
|     buf_size +=
 | |
|         snprintf(NULL, 0, "%s: %s\n", http_header_names[res->headers[i].type],
 | |
|                  res->headers[i].value);
 | |
|   }
 | |
| 
 | |
|   // The + 1 is required to store the final null byte, but we will replace it
 | |
|   // with the required final newline
 | |
|   char *buf = malloc(buf_size + 1);
 | |
|   buf_size = sprintf(buf, http_response_format, res->type, response_type_name,
 | |
|                      res->body_len);
 | |
| 
 | |
|   for (size_t i = 0; i < res->header_count; i++) {
 | |
|     buf_size +=
 | |
|         sprintf(&buf[buf_size], "%s: %s\n",
 | |
|                 http_header_names[res->headers[i].type], res->headers[i].value);
 | |
|   }
 | |
| 
 | |
|   buf[buf_size] = '\n';
 | |
| 
 | |
|   res->head = buf;
 | |
|   res->head_len = buf_size + 1;
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|     http_loop_init_header(res);
 | |
|   }
 | |
| 
 | |
|   // 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);
 | |
|     size_t bytes_written;
 | |
| 
 | |
|     switch (res->body_type) {
 | |
|     case http_response_body_buf:
 | |
|       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;
 | |
|       break;
 | |
|     case http_response_body_file:
 | |
|       bytes_written = fread(&conn->wbuf[conn->wbuf_size], sizeof(uint8_t),
 | |
|                             bytes_to_write, res->body);
 | |
|       conn->wbuf_size += bytes_written;
 | |
|       res->body_written += bytes_written;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void http_loop_res_set_body_buf(http_loop_ctx *ctx, const char *body,
 | |
|                                 size_t body_len, bool owned) {
 | |
|   ctx->res.body_type = http_response_body_buf;
 | |
|   ctx->res.body = (void *)body;
 | |
|   ctx->res.body_len = body_len;
 | |
|   ctx->res.owns_body = owned;
 | |
| }
 | |
| 
 | |
| void http_loop_res_set_body_file(http_loop_ctx *ctx, const char *filename) {
 | |
|   struct stat st;
 | |
|   stat(filename, &st);
 | |
| 
 | |
|   // TODO error handling
 | |
|   FILE *f = fopen(filename, "r");
 | |
| 
 | |
|   ctx->res.body_type = http_response_body_file;
 | |
|   ctx->res.body = f;
 | |
|   ctx->res.body_len = st.st_size;
 | |
| }
 | |
| 
 | |
| void http_loop_res_add_header(http_loop_ctx *ctx, http_header type,
 | |
|                               const char *value, bool owned) {
 | |
|   ctx->res.headers[ctx->res.header_count].type = type;
 | |
|   ctx->res.headers[ctx->res.header_count].value = value;
 | |
|   ctx->res.headers[ctx->res.header_count].owned = owned;
 | |
| 
 | |
|   ctx->res.header_count++;
 | |
| }
 |