├── .gitignore ├── LICENSE ├── README.md ├── example.c ├── example2.c ├── xhttp.c └── xhttp.h /.gitignore: -------------------------------------------------------------------------------- 1 | example 2 | example2 3 | vgcore.* 4 | jmeter.log 5 | RFC/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Francesco Cozzuto 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xHTTP 2 | xHTTP is an HTTP server library designed to be lightweight, fast, robust and easy to use. 3 | 4 | **NOTE**: It's still in the early sperimentation phase (I'm still figuring things out!) 5 | 6 | ## Features 7 | xHTTP's more relevant features are: 8 | - It's fast 9 | - HTTP/1.1 10 | - Supports `Connection: Keep-Alive` 11 | - Uses `sendfile` 12 | - No global state 13 | - Single-threaded 14 | - Based on Linux's epoll 15 | - No dependencies (other than Linux and the standard library) 16 | 17 | while some notably missing features are: 18 | - Only works on Linux 19 | - Doesn't support `Transfer-Encoding: Chunked` 20 | - No IPv6 21 | 22 | ## Installation 23 | The way you install it is by just copying `xhttp.c` and `xhttp.h` in your source tree and compiling it like it was one of your C files: include the `xhttp.h` where you want to use it and compile `xhttp.c` with your files. 24 | 25 | ## Usage 26 | To start a server instance, you need to call the `xhttp` function 27 | ```c 28 | const char *xhttp(const char *addr, unsigned short port, 29 | xh_callback callback, xh_handle *handle, 30 | const xh_config *config); 31 | ``` 32 | by providing it with a callback that generates the HTTP response that the library will forward to the user. 33 | 34 | The callback's interface must be 35 | ```c 36 | void callback(xh_request *req, xh_response *res, void *userp); 37 | ``` 38 | The request information is provided through the `req` argument, while `res` is an output argument. The callback will respond to the request by setting the fields of `res`. These two arguments are never `NULL`. 39 | 40 | Here's an example of a basic server which always responds with a "Hello, world!" message: 41 | 42 | ```c 43 | #include 44 | #include "xhttp.h" 45 | 46 | static void callback(xh_request *req, xh_response *res, void *userp) 47 | { 48 | (void) req; 49 | (void) userp; 50 | res->status = 200; 51 | res->body.str = "Hello, world!"; 52 | xh_header_add(res, "Content-Type", "text/plain"); 53 | } 54 | 55 | int main() 56 | { 57 | const char *error = xhttp(NULL, 8080, callback, 58 | NULL, NULL, NULL); 59 | if(error != NULL) 60 | { 61 | fprintf(stderr, "ERROR: %s\n", error); 62 | return 1; 63 | } 64 | fprintf(stderr, "OK\n"); 65 | return 0; 66 | } 67 | 68 | ``` 69 | 70 | if this were your `main.c` file, you'd compile it with 71 | ```sh 72 | $ gcc main.c xhttp.c -o main 73 | ``` 74 | 75 | You can find a slightly more complete example in `example.c`. 76 | 77 | ## Contributing 78 | Feel free to propose any changes or send in patches! Though I'd advise to open an issue before sending any non-trivial changes, just to make sure we're on the same page first! -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | 2 | // Build with: 3 | // $ gcc example.c xhttp.c -o example 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "xhttp.h" 9 | 10 | static xh_handle handle; 11 | 12 | static void callback(xh_request *req, xh_response *res, void *userp) 13 | { 14 | (void) req; 15 | (void) userp; 16 | 17 | res->status = 200; 18 | if(!strcmp(req->URL.str, "/file")) 19 | res->file = "example.c"; 20 | else 21 | res->body.str = "Hello, world!"; 22 | xh_header_add(res, "Content-Type", "text/plain"); 23 | } 24 | 25 | static void handle_sigterm(int signum) 26 | { 27 | (void) signum; 28 | xh_quit(handle); 29 | } 30 | 31 | int main() 32 | { 33 | signal(SIGTERM, handle_sigterm); 34 | signal(SIGQUIT, handle_sigterm); 35 | signal(SIGINT, handle_sigterm); 36 | 37 | const char *error = xhttp(NULL, 8080, callback, 38 | NULL, &handle, NULL); 39 | if(error != NULL) 40 | { 41 | fprintf(stderr, "ERROR: %s\n", error); 42 | return 1; 43 | } 44 | fprintf(stderr, "OK\n"); 45 | return 0; 46 | } -------------------------------------------------------------------------------- /example2.c: -------------------------------------------------------------------------------- 1 | 2 | // Build with: 3 | // $ gcc example2.c xhttp.c -o example2 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "xhttp.h" 10 | 11 | static xh_handle handle; 12 | static char buffer[1024]; 13 | 14 | static void callback(xh_request *req, xh_response *res, void *userp) 15 | { 16 | (void) req; 17 | (void) userp; 18 | 19 | int post; 20 | char username[32]; 21 | 22 | if(!xh_urlcmp(req->URL.str, "/users/:s", 23 | sizeof(username), username)) { 24 | 25 | snprintf(buffer, sizeof(buffer), "Hello, %s!\n", username); 26 | res->status = 200; 27 | res->body.str = buffer; 28 | 29 | } else if(!xh_urlcmp(req->URL.str, "/users/:s/posts/:d", 30 | sizeof(username), username, &post)) { 31 | 32 | snprintf(buffer, sizeof(buffer), "Hello, %s! You asked for post no. %d!\n", username, post); 33 | res->status = 200; 34 | res->body.str = buffer; 35 | 36 | } else { 37 | res->status = 404; 38 | res->body.str = "It seems like what you're looking for isn't here! :S"; 39 | } 40 | xh_header_add(res, "Content-Type", "text/plain"); 41 | } 42 | 43 | static void handle_sigterm(int signum) 44 | { 45 | (void) signum; 46 | xh_quit(handle); 47 | } 48 | 49 | int main() 50 | { 51 | signal(SIGTERM, handle_sigterm); 52 | signal(SIGQUIT, handle_sigterm); 53 | signal(SIGINT, handle_sigterm); 54 | 55 | const char *error = xhttp(NULL, 8080, callback, 56 | NULL, &handle, NULL); 57 | if(error != NULL) 58 | { 59 | fprintf(stderr, "ERROR: %s\n", error); 60 | return 1; 61 | } 62 | fprintf(stderr, "OK\n"); 63 | return 0; 64 | } -------------------------------------------------------------------------------- /xhttp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "xhttp.h" 21 | 22 | 23 | /* __ _________________ * 24 | * __ __ / // /_ __/_ __/ _ \ * 25 | * \ \ // _ / / / / / / ___/ * 26 | * /_\_\/_//_/ /_/ /_/ /_/ * 27 | * * 28 | * +--------------------------------------------------------------------------------------------+ * 29 | * | | * 30 | * | OVERVIEW | * 31 | * | | * 32 | * | The logic starts inside the [xhttp] function, where the server waits in a loop for events | * 33 | * | provided by epoll (the event loop). | * 34 | * | | * 35 | * | Each client connection is represented by a [conn_t] structure, which is basically composed | * 36 | * | by a buffer of input data, a buffer of output data, the parsing state of the input buffer | * 37 | * | plus some more fields required to hold the state of the parsing and to manage the | * 38 | * | connection. These structures are preallocated at start-up time and determine the capacity | * 39 | * | of the server. | * 40 | * | | * 41 | * | Whenever a client requests to connect, the server decides if it can handle it or not. If | * 42 | * | it can, it gives it a [conn_t] structure and registers it into the event loop. | * 43 | * | | * 44 | * | When the event loop signals that a connection sent some data, the data is copied from the | * 45 | * | kernel into the user-space buffer of the [conn_t] structure. If the head of the request | * 46 | * | wasn't received or was received partially, the character sequence "\r\n\r\n" (a blank line)| * 47 | * | is searched for inside the downloaded data. The "\r\n\r\n" token signifies the end of the | * 48 | * | request's head and the start of it's body. If the head wasn't received the server goes | * 49 | * | back to waiting for new events. If the token is found, the head can be parsed and the size | * 50 | * | of the body determined. If the whole body of the request was received with the head, the | * 51 | * | request can already be handled. If the body wasn't received, the servers goes back to | * 52 | * | waiting for events until the rest of the body is received. When the body is fully received,| * 53 | * | the user-provided callback can be called to generate a response. | * 54 | * | One thing to note is that multiple requests could be read from a single [recv], making it | * 55 | * | necessary to perform these operations on the input buffer in a loop. | * 56 | * | | * 57 | * | If at any point of this process the request is determined to be invalid or an internal | * 58 | * | error occurres, a 4xx or 5xx response is sent. | * 59 | * | | * 60 | * | While handling data input events, the response is never sent directly to the kernel buffer,| * 61 | * | because the call to [send] could block the server. Instead, the response is written to the | * 62 | * | [conn_t]'s output buffer. This buffer is only flushed to the kernel when a write-ready | * 63 | * | event is triggered for that connection. | * 64 | * | | * 65 | * +--------------------------------------------------------------------------------------------+ * 66 | * */ 67 | 68 | typedef enum { 69 | XH_REQ, 70 | XH_RES 71 | } struct_type_t; 72 | 73 | typedef struct { 74 | struct_type_t type; 75 | xh_response public; 76 | xh_table headers; 77 | int capacity; 78 | bool failed; 79 | } xh_response2; 80 | 81 | typedef struct { 82 | struct_type_t type; 83 | xh_request public; 84 | } xh_request2; 85 | 86 | typedef struct { 87 | char *data; 88 | uint32_t size; 89 | uint32_t used; 90 | } buffer_t; 91 | 92 | typedef struct conn_t conn_t; 93 | struct conn_t { 94 | 95 | // This is used to hold a free-list 96 | // of [conn_t] structures. 97 | conn_t *next; 98 | 99 | // I/O buffers required for async. 100 | // reads and writes. 101 | buffer_t in, out; 102 | 103 | // Connection's socked file 104 | // descriptor. 105 | int fd; 106 | 107 | // Number of resources served to 108 | // this client. This is used to 109 | // determine which connections to 110 | // keep alive. 111 | int served; 112 | 113 | // This flags can be set after a 114 | // response is written to the output 115 | // buffer. If set, then all reads 116 | // from the client stop and when the 117 | // output buffer is flushed the 118 | // connection is closed. 119 | bool close_when_uploaded; 120 | 121 | // The way writes to the output buffer occur is 122 | // through the [append_string_to_output_buffer] 123 | // function. Since the output buffer may beed to 124 | // be resized, the [append_string_to_output_buffer] 125 | // operation may fail. Since checking every time 126 | // for the return value makes the code very verbose, 127 | // instead of returning an error value, this flag 128 | // is set. If this flag is set then 129 | // [append_string_to_output_buffer] operations 130 | // have no effect and when [upload] is called it 131 | // returns the error that the first 132 | // [append_string_to_output_buffer] that failed 133 | // would have returned. 134 | bool failed_to_append; 135 | 136 | bool sending_from_fd; 137 | int file_fd; 138 | int file_off; 139 | int file_len; 140 | 141 | bool head_received; 142 | uint32_t body_offset; 143 | uint32_t body_length; 144 | xh_request2 request; 145 | }; 146 | 147 | typedef struct { 148 | bool exiting; 149 | int fd, epfd, maxconns, connum; 150 | conn_t *pool, *freelist; 151 | xh_callback callback; 152 | void *userp; 153 | } context_t; 154 | 155 | static const char *statis_code_to_status_text(int code) 156 | { 157 | switch(code) 158 | { 159 | case 100: return "Continue"; 160 | case 101: return "Switching Protocols"; 161 | case 102: return "Processing"; 162 | 163 | case 200: return "OK"; 164 | case 201: return "Created"; 165 | case 202: return "Accepted"; 166 | case 203: return "Non-Authoritative Information"; 167 | case 204: return "No Content"; 168 | case 205: return "Reset Content"; 169 | case 206: return "Partial Content"; 170 | case 207: return "Multi-Status"; 171 | case 208: return "Already Reported"; 172 | 173 | case 300: return "Multiple Choices"; 174 | case 301: return "Moved Permanently"; 175 | case 302: return "Found"; 176 | case 303: return "See Other"; 177 | case 304: return "Not Modified"; 178 | case 305: return "Use Proxy"; 179 | case 306: return "Switch Proxy"; 180 | case 307: return "Temporary Redirect"; 181 | case 308: return "Permanent Redirect"; 182 | 183 | case 400: return "Bad Request"; 184 | case 401: return "Unauthorized"; 185 | case 402: return "Payment Required"; 186 | case 403: return "Forbidden"; 187 | case 404: return "Not Found"; 188 | case 405: return "Method Not Allowed"; 189 | case 406: return "Not Acceptable"; 190 | case 407: return "Proxy Authentication Required"; 191 | case 408: return "Request Timeout"; 192 | case 409: return "Conflict"; 193 | case 410: return "Gone"; 194 | case 411: return "Length Required"; 195 | case 412: return "Precondition Failed"; 196 | case 413: return "Request Entity Too Large"; 197 | case 414: return "Request-URI Too Long"; 198 | case 415: return "Unsupported Media Type"; 199 | case 416: return "Requested Range Not Satisfiable"; 200 | case 417: return "Expectation Failed"; 201 | case 418: return "I'm a teapot"; 202 | case 420: return "Enhance your calm"; 203 | case 422: return "Unprocessable Entity"; 204 | case 426: return "Upgrade Required"; 205 | case 429: return "Too many requests"; 206 | case 431: return "Request Header Fields Too Large"; 207 | case 449: return "Retry With"; 208 | case 451: return "Unavailable For Legal Reasons"; 209 | 210 | case 500: return "Internal Server Error"; 211 | case 501: return "Not Implemented"; 212 | case 502: return "Bad Gateway"; 213 | case 503: return "Service Unavailable"; 214 | case 504: return "Gateway Timeout"; 215 | case 505: return "HTTP Version Not Supported"; 216 | case 509: return "Bandwidth Limit Exceeded"; 217 | } 218 | return "???"; 219 | } 220 | 221 | /* Symbol: find_header 222 | * 223 | * Finds the header from a header array. 224 | * 225 | * Arguments: 226 | * 227 | * - headers: The header set array. 228 | * 229 | * - name: Zero-terminated string that contains the 230 | * header's name. The comparison with each 231 | * header's name is made using [xh_header_cmp], 232 | * so it's not case-sensitive. 233 | * 234 | * Returns: 235 | * The index in the array of the matched header, or 236 | * -1 is no header was found. 237 | */ 238 | static int find_header(xh_table headers, const char *name) 239 | { 240 | for(int i = 0; i < headers.count; i += 1) 241 | if(xh_header_cmp(name, headers.list[i].key.str)) 242 | return i; 243 | return -1; 244 | } 245 | 246 | /* Symbol: xh_header_add 247 | * 248 | * Add or replace a header into a response object. 249 | * 250 | * Arguments: 251 | * 252 | * - res: The response object. 253 | * 254 | * - name: Zero-terminated string that contains 255 | * the header's name. The comparison with 256 | * each header's name is made using [xh_header_cmp], 257 | * so it's not case-sensitive. 258 | * 259 | * - valfmt: A printf-like format string that evaluates 260 | * to the header's value. 261 | * 262 | * Returns: 263 | * Nothing. The header may or may not be added 264 | * (or replaced) to the request. 265 | */ 266 | void xh_header_add(xh_response *res, const char *name, const char *valfmt, ...) 267 | { 268 | xh_response2 *res2 = (xh_response2*) ((char*) res - offsetof(xh_response2, public)); 269 | 270 | assert(&res2->public == res); 271 | 272 | if(res2->failed) 273 | return; 274 | 275 | int i = find_header(res2->headers, name); 276 | 277 | unsigned int name_len, value_len; 278 | 279 | name_len = name == NULL ? 0 : strlen(name); 280 | 281 | char value[512]; 282 | { 283 | va_list args; 284 | va_start(args, valfmt); 285 | int n = vsnprintf(value, sizeof(value), valfmt, args); 286 | va_end(args); 287 | 288 | if(n < 0) 289 | { 290 | // Bad format. 291 | res2->failed = 1; 292 | return; 293 | } 294 | 295 | if((unsigned int) n >= sizeof(value)) 296 | { 297 | // Static buffer is too small. 298 | res2->failed = 1; 299 | return; 300 | } 301 | 302 | value_len = n; 303 | } 304 | 305 | // Duplicate name and value. 306 | char *name2, *value2; 307 | { 308 | void *mem = malloc(name_len + value_len + 2); 309 | 310 | if(mem == NULL) 311 | { 312 | // ERROR! 313 | res2->failed = 1; 314 | return; 315 | } 316 | 317 | name2 = (char*) mem; 318 | value2 = (char*) mem + name_len + 1; 319 | 320 | strcpy(name2, name); 321 | strcpy(value2, value); 322 | } 323 | 324 | if(i < 0) 325 | { 326 | if(res2->headers.count == res2->capacity) 327 | { 328 | int new_capacity = res2->capacity == 0 329 | ? 8 : res2->capacity * 2; 330 | 331 | void *tmp = realloc(res2->headers.list, 332 | new_capacity * sizeof(xh_pair)); 333 | 334 | if(tmp == NULL) 335 | { 336 | // ERROR! 337 | res2->failed = 1; 338 | free(name2); 339 | return; 340 | } 341 | 342 | res2->headers.list = tmp; 343 | res2->capacity = new_capacity; 344 | } 345 | 346 | res2->headers.list[res2->headers.count] = (xh_pair) { 347 | { name2, name_len }, { value2, value_len }, 348 | }; 349 | res2->headers.count += 1; 350 | res2->public.headers = res2->headers; 351 | } 352 | else 353 | { 354 | free(res2->headers.list[i].key.str); 355 | res2->headers.list[i] = (xh_pair) { 356 | { name2, name_len }, { value2, value_len }, 357 | }; 358 | } 359 | } 360 | 361 | /* Symbol: xh_header_rem 362 | * 363 | * Remove a header from a response object. 364 | * 365 | * Arguments: 366 | * 367 | * - res: The response object that contains the 368 | * header to be removed. 369 | * 370 | * - name: Zero-terminated string that contains 371 | * the header's name. The comparison with 372 | * each header's name is made using [xh_header_cmp], 373 | * so it's not case-sensitive. 374 | * 375 | * Returns: 376 | * Nothing. 377 | */ 378 | void xh_header_rem(xh_response *res, const char *name) 379 | { 380 | xh_response2 *res2 = (xh_response2*) ((char*) res - offsetof(xh_response2, public)); 381 | 382 | assert(&res2->public == res); 383 | 384 | if(res2->failed) 385 | return; 386 | 387 | int i = find_header(res2->headers, name); 388 | 389 | if(i < 0) 390 | return; 391 | 392 | free(res2->headers.list[i].key.str); 393 | 394 | assert(i >= 0); 395 | 396 | for(; i < res2->headers.count-1; i += 1) 397 | res2->headers.list[i] = res2->headers.list[i+1]; 398 | 399 | res2->headers.count -= 1; 400 | res2->public.headers = res2->headers; 401 | } 402 | 403 | static xh_table get_headers_from_req_or_res(void *req_or_res) 404 | { 405 | _Static_assert(offsetof(xh_response2, public) == offsetof(xh_request2, public), 406 | "The public portion of xh_response2 and xh_request2 must be aligned the same way"); 407 | 408 | struct_type_t type = ((xh_request2*) ((char*) req_or_res 409 | - offsetof(xh_request2, public)))->type; 410 | 411 | assert(type == XH_RES || type == XH_REQ); 412 | 413 | xh_table headers = (type == XH_REQ)? 414 | ((xh_request *) req_or_res)->headers: 415 | ((xh_response*) req_or_res)->headers; 416 | return headers; 417 | } 418 | 419 | /* Symbol: xh_header_get 420 | * 421 | * Find the contents of a header given it's 422 | * name from a response or request object. 423 | * 424 | * Arguments: 425 | * 426 | * - req_or_res: The request or response object 427 | * that contains the header. This 428 | * argument must originally be of 429 | * type [xh_request*] or [xh_response*]. 430 | * 431 | * - name: Zero-terminated string that contains 432 | * the header's name. The comparison with 433 | * each header's name is made using [xh_header_cmp], 434 | * so it's not case-sensitive. 435 | * 436 | * Returns: 437 | * A zero-terminated string containing the value of 438 | * the header or NULL if the header isn't contained 439 | * in the request/response. 440 | * 441 | * Notes: 442 | * - The returned value is invalidated if 443 | * the header is removed using [xh_hrem]. 444 | */ 445 | const char *xh_header_get(void *req_or_res, const char *name) 446 | { 447 | xh_table headers = get_headers_from_req_or_res(req_or_res); 448 | 449 | int i = find_header(headers, name); 450 | 451 | if(i < 0) 452 | return NULL; 453 | 454 | return headers.list[i].val.str; 455 | } 456 | 457 | /* Symbol: xh_header_cmp 458 | * 459 | * This function compares header names. 460 | * The comparison isn't case-sensitive. 461 | * 462 | * Arguments: 463 | * 464 | * - a: Zero-terminated string that contains 465 | * the first header's name. 466 | * 467 | * - b: Zero-terminated string that contains 468 | * the second header's name. 469 | * 470 | * Returns: 471 | * 1 if the header names match, 0 otherwise. 472 | */ 473 | bool xh_header_cmp(const char *a, const char *b) 474 | { 475 | if(a == NULL || b == NULL) 476 | return a == b; 477 | 478 | while(*a != '\0' && *b != '\0' && tolower(*a) == tolower(*b)) 479 | a += 1, b += 1; 480 | 481 | return tolower(*a) == tolower(*b); 482 | } 483 | 484 | static void res_init(xh_response2 *res) 485 | { 486 | memset(res, 0, sizeof(xh_response2)); 487 | res->type = XH_RES; 488 | res->public.body.len = -1; 489 | } 490 | 491 | static void res_deinit(xh_response2 *res) 492 | { 493 | if(res->headers.list != NULL) 494 | { 495 | assert(res->headers.count > 0); 496 | for(int i = 0; i < res->headers.count; i += 1) 497 | free(res->headers.list[i].key.str); 498 | free(res->headers.list); 499 | res->headers.list = NULL; 500 | } 501 | } 502 | 503 | static void res_reinit(xh_response2 *res) 504 | { 505 | res_deinit(res); 506 | res_init(res); 507 | } 508 | 509 | static void req_init(xh_request2 *req) 510 | { 511 | req->type = XH_REQ; 512 | } 513 | 514 | static void req_deinit(xh_request *req) 515 | { 516 | free(req->headers.list); 517 | req->headers.list = NULL; 518 | req->headers.count = 0; 519 | } 520 | 521 | static bool set_non_blocking(int fd) 522 | { 523 | int flags = fcntl(fd, F_GETFL); 524 | 525 | if(flags < 0) 526 | return 0; 527 | 528 | return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0; 529 | } 530 | 531 | static void accept_connection(context_t *ctx) 532 | { 533 | int cfd = accept(ctx->fd, NULL, NULL); 534 | 535 | if(cfd < 0) 536 | return; // Failed to accept. 537 | 538 | if(!set_non_blocking(cfd)) 539 | { 540 | (void) close(cfd); 541 | return; 542 | } 543 | 544 | if(ctx->freelist == NULL) 545 | { 546 | // Connection limit reached. 547 | (void) close(cfd); 548 | return; 549 | } 550 | 551 | conn_t *conn = ctx->freelist; 552 | ctx->freelist = conn->next; 553 | 554 | assert(((intptr_t) conn & 555 | (intptr_t) 1) == 0); 556 | 557 | memset(conn, 0, sizeof(conn_t)); 558 | conn->fd = cfd; 559 | req_init(&conn->request); 560 | 561 | struct epoll_event buffer; 562 | buffer.events = EPOLLET | EPOLLIN 563 | | EPOLLPRI | EPOLLOUT 564 | | EPOLLRDHUP; 565 | buffer.data.ptr = conn; 566 | if(epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, cfd, &buffer)) 567 | { 568 | (void) close(cfd); 569 | 570 | conn->fd = -1; 571 | conn->next = ctx->freelist; 572 | ctx->freelist = conn; 573 | return; 574 | } 575 | 576 | ctx->connum += 1; 577 | } 578 | 579 | static void close_connection(context_t *ctx, conn_t *conn) 580 | { 581 | (void) close(conn->fd); 582 | 583 | if(conn->in.data != NULL) 584 | { 585 | free(conn->in.data); 586 | conn->in.data = NULL; 587 | } 588 | 589 | if(conn->out.data != NULL) 590 | { 591 | free(conn->out.data); 592 | conn->out.data = NULL; 593 | } 594 | 595 | if(conn->request.public.headers.list != NULL) 596 | free(conn->request.public.headers.list); 597 | 598 | conn->fd = -1; 599 | 600 | conn->next = ctx->freelist; 601 | ctx->freelist = conn; 602 | 603 | ctx->connum -= 1; 604 | } 605 | 606 | #if DEBUG 607 | static void close_connection_(context_t *ctx, conn_t *conn, const char *file, int line) 608 | { 609 | fprintf(stderr, "Closing connection at %s:%d.\n", file, line); 610 | close_connection(ctx, conn); 611 | } 612 | #define close_connection(ctx, conn) close_connection_(ctx, conn, __FILE__, __LINE__) 613 | #endif 614 | 615 | static bool is_uppercase_alpha(char c) 616 | { 617 | return c >= 'A' && c <= 'Z'; 618 | } 619 | 620 | static bool is_digit(char c) 621 | { 622 | return c >= '0' && c <= '9'; 623 | } 624 | 625 | static bool is_space(char c) 626 | { 627 | return c == ' '; 628 | } 629 | 630 | static void skip(char *str, uint32_t len, uint32_t *i, bool not, bool (*test)(char)) 631 | { 632 | if(not) 633 | while(*i < len && !test(str[*i])) 634 | *i += 1; 635 | else 636 | while(*i < len && test(str[*i])) 637 | *i += 1; 638 | } 639 | 640 | static void skip_until(char *str, uint32_t len, uint32_t *i, char c) 641 | { 642 | while(*i < len && str[*i] != c) 643 | *i += 1; 644 | } 645 | 646 | struct parse_err_t { 647 | bool internal; 648 | char *msg; 649 | unsigned int len; 650 | }; 651 | 652 | static struct parse_err_t parse(char *str, uint32_t len, xh_request *req) 653 | { 654 | #define OK \ 655 | ((struct parse_err_t) { .internal = 0, .msg = NULL}) 656 | 657 | #define FAILURE(msg_) \ 658 | ((struct parse_err_t) { .internal = 0, .msg = msg_, .len = sizeof(msg_)-1 }) 659 | 660 | #define INTERNAL_FAILURE(msg_) \ 661 | ((struct parse_err_t) { .internal = 1, .msg = msg_, .len = sizeof(msg_)-1 }) 662 | 663 | if(len == 0) 664 | return FAILURE("Empty request"); 665 | 666 | uint32_t i = 0; 667 | 668 | uint32_t method_offset = i; 669 | 670 | skip(str, len, &i, 0, is_uppercase_alpha); 671 | 672 | uint32_t method_length = i - method_offset; 673 | 674 | if(method_length == 0) 675 | return FAILURE("Missing method"); 676 | 677 | if(i == len) 678 | return FAILURE("Missing URL and HTTP version"); 679 | 680 | if(!is_space(str[i])) 681 | return FAILURE("Bad character after method. Methods can only have uppercase alphabetic characters"); 682 | 683 | skip(str, len, &i, 0, is_space); 684 | 685 | if(i == len) 686 | return FAILURE("Missing URL and HTTP version"); 687 | 688 | uint32_t URL_offset = i; 689 | while(i < len && str[i] != ' ' && str[i] != '?') 690 | i += 1; 691 | uint32_t URL_length = i - URL_offset; 692 | 693 | uint32_t params_offset; 694 | if(i < len && str[i] == '?') 695 | { 696 | params_offset = i+1; 697 | while(i < len && str[i] != ' ') 698 | i += 1; 699 | } 700 | else params_offset = i; 701 | uint32_t params_length = i - params_offset; 702 | 703 | if(i == len) 704 | return FAILURE("Missing HTTP version"); 705 | 706 | assert(is_space(str[i])); 707 | 708 | skip(str, len, &i, 0, is_space); 709 | 710 | if(i == len) 711 | return FAILURE("Missing HTTP version"); 712 | 713 | uint32_t version_offset = i; 714 | 715 | skip_until(str, len, &i, '\r'); 716 | 717 | uint32_t version_length = i - version_offset; 718 | 719 | if(version_length == 0) 720 | return FAILURE("Missing HTTP version"); 721 | 722 | if(i == len) 723 | return FAILURE("Missing CRLF after HTTP version"); 724 | 725 | assert(str[i] == '\r'); 726 | 727 | i += 1; // Skip the \r. 728 | 729 | if(i == len) 730 | return FAILURE("Missing LF after CR"); 731 | 732 | if(str[i] != '\n') 733 | return FAILURE("Missing LF after CR"); 734 | 735 | i += 1; // Skip the \n. 736 | 737 | int capacity = 0; 738 | xh_table headers = { 739 | .list = NULL, .count = 0 }; 740 | 741 | while(1) 742 | { 743 | if(i == len) 744 | { 745 | free(headers.list); 746 | return FAILURE("Missing blank line"); 747 | } 748 | 749 | if(i+1 < len && str[i] == '\r' && str[i+1] == '\n') 750 | { 751 | // Blank line. 752 | i += 2; 753 | break; 754 | } 755 | 756 | uint32_t hname_offset = i; 757 | 758 | skip_until(str, len, &i, ':'); 759 | 760 | uint32_t hname_length = i - hname_offset; 761 | 762 | if(i == len) 763 | { 764 | free(headers.list); 765 | return FAILURE("Malformed header"); 766 | } 767 | 768 | if(hname_length == 0) 769 | { 770 | free(headers.list); 771 | return FAILURE("Empty header name"); 772 | } 773 | 774 | assert(str[i] == ':'); 775 | 776 | // Make the header name zero-terminated 777 | // by overwriting the ':' with a '\0'. 778 | str[i] = '\0'; 779 | 780 | i += 1; // Skip the ':'. 781 | 782 | uint32_t hvalue_offset = i; 783 | 784 | do 785 | { 786 | skip_until(str, len, &i, '\r'); 787 | 788 | if(i == len) 789 | { 790 | free(headers.list); 791 | return FAILURE("Malformed header"); 792 | } 793 | 794 | assert(str[i] == '\r'); 795 | 796 | i += 1; // Skip the \r. 797 | 798 | if(i == len) 799 | { 800 | free(headers.list); 801 | return FAILURE("Malformed header"); 802 | } 803 | } 804 | while(str[i] != '\n'); 805 | assert(str[i] == '\n'); 806 | i += 1; // Skip the '\n'. 807 | 808 | uint32_t hvalue_length = (i - 2) - hvalue_offset; 809 | 810 | if(headers.count == capacity) 811 | { 812 | int new_capacity = capacity == 0 ? 8 : capacity * 2; 813 | 814 | void *temp = realloc(headers.list, 815 | new_capacity * sizeof(xh_pair)); 816 | 817 | if(temp == NULL) 818 | { 819 | free(headers.list); 820 | return INTERNAL_FAILURE("No memory"); 821 | } 822 | 823 | capacity = new_capacity; 824 | headers.list = temp; 825 | } 826 | 827 | headers.list[headers.count++] = (xh_pair) { 828 | { str + hname_offset, hname_length }, 829 | { str + hvalue_offset, hvalue_length }, 830 | }; 831 | 832 | str[ hname_offset + hname_length] = '\0'; 833 | str[hvalue_offset + hvalue_length] = '\0'; 834 | } 835 | 836 | req->headers = headers; 837 | 838 | req->method = xh_string_new(str + method_offset, method_length); 839 | req->URL = xh_string_new(str + URL_offset, URL_length); 840 | req->params = xh_string_new(str + params_offset, params_length); 841 | 842 | str[ method_offset + method_length] = '\0'; 843 | str[ URL_offset + URL_length] = '\0'; 844 | str[ params_offset + params_length] = '\0'; 845 | str[version_offset + version_length] = '\0'; 846 | 847 | // Validate the header. 848 | { 849 | bool unknown_method = 0; 850 | 851 | #define PAIR(p, q) (uint64_t) (((uint64_t) p << 32) | (uint64_t) q) 852 | switch(PAIR(req->method.str[0], method_length)) 853 | { 854 | case PAIR('G', 3): req->method_id = XH_GET; unknown_method = !!strcmp(req->method.str, "GET"); break; 855 | case PAIR('H', 4): req->method_id = XH_HEAD; unknown_method = !!strcmp(req->method.str, "HEAD"); break; 856 | case PAIR('P', 4): req->method_id = XH_POST; unknown_method = !!strcmp(req->method.str, "POST"); break; 857 | case PAIR('P', 3): req->method_id = XH_PUT; unknown_method = !!strcmp(req->method.str, "PUT"); break; 858 | case PAIR('D', 6): req->method_id = XH_DELETE; unknown_method = !!strcmp(req->method.str, "DELETE"); break; 859 | case PAIR('C', 7): req->method_id = XH_CONNECT; unknown_method = !!strcmp(req->method.str, "CONNECT"); break; 860 | case PAIR('O', 7): req->method_id = XH_OPTIONS; unknown_method = !!strcmp(req->method.str, "OPTIONS"); break; 861 | case PAIR('T', 5): req->method_id = XH_TRACE; unknown_method = !!strcmp(req->method.str, "TRACE"); break; 862 | case PAIR('P', 5): req->method_id = XH_PATCH; unknown_method = !!strcmp(req->method.str, "PATCH"); break; 863 | default: unknown_method = 1; break; 864 | } 865 | #undef PAIR 866 | 867 | if(unknown_method) 868 | { 869 | free(headers.list); 870 | req->headers.list = NULL; 871 | return FAILURE("Unknown method"); 872 | } 873 | } 874 | 875 | // Validate the HTTP version 876 | { 877 | bool bad_version = 0; 878 | switch(version_length) 879 | { 880 | case sizeof("HTTP/M.N")-1: 881 | 882 | if(!strcmp(str + version_offset, "HTTP/0.9")) 883 | { 884 | req->version_major = 0; 885 | req->version_minor = 9; 886 | break; 887 | } 888 | 889 | if(!strcmp(str + version_offset, "HTTP/1.0")) 890 | { 891 | req->version_major = 1; 892 | req->version_minor = 0; 893 | break; 894 | } 895 | 896 | if(!strcmp(str + version_offset, "HTTP/1.1")) 897 | { 898 | req->version_major = 1; 899 | req->version_minor = 1; 900 | break; 901 | } 902 | 903 | if(!strcmp(str + version_offset, "HTTP/2.0")) 904 | { 905 | req->version_major = 2; 906 | req->version_minor = 0; 907 | break; 908 | } 909 | 910 | if(!strcmp(str + version_offset, "HTTP/3.0")) 911 | { 912 | req->version_major = 3; 913 | req->version_minor = 0; 914 | break; 915 | } 916 | 917 | bad_version = 1; 918 | break; 919 | 920 | case sizeof("HTTP/M")-1: 921 | 922 | if(!strcmp(str + version_offset, "HTTP/1")) 923 | { 924 | req->version_major = 1; 925 | req->version_minor = 0; 926 | break; 927 | } 928 | 929 | if(!strcmp(str + version_offset, "HTTP/2")) 930 | { 931 | req->version_major = 2; 932 | req->version_minor = 0; 933 | break; 934 | } 935 | 936 | if(!strcmp(str + version_offset, "HTTP/3")) 937 | { 938 | req->version_major = 3; 939 | req->version_minor = 0; 940 | break; 941 | } 942 | 943 | bad_version = 1; 944 | break; 945 | 946 | default: 947 | bad_version = 1; 948 | break; 949 | } 950 | 951 | if(bad_version) 952 | { 953 | free(headers.list); 954 | req->headers.list = NULL; 955 | return FAILURE("Bad HTTP version"); 956 | } 957 | } 958 | 959 | return OK; 960 | 961 | #undef OK 962 | #undef FAILURE 963 | #undef INTERNAL_FAILURE 964 | } 965 | 966 | static bool upload(conn_t *conn) 967 | { 968 | if(conn->failed_to_append) 969 | return 0; 970 | 971 | 972 | if(conn->out.used > 0) 973 | { 974 | /* Flush the output buffer. */ 975 | uint32_t sent, total; 976 | 977 | sent = 0; 978 | total = conn->out.used; 979 | 980 | if(total == 0) 981 | return 1; 982 | 983 | while(sent < total) 984 | { 985 | int n = send(conn->fd, conn->out.data + sent, total - sent, 0); 986 | 987 | if(n < 0) 988 | { 989 | if(errno == EAGAIN || errno == EWOULDBLOCK) 990 | break; 991 | 992 | // ERROR! 993 | return 0; 994 | } 995 | 996 | assert(n >= 0); 997 | sent += n; 998 | } 999 | 1000 | memmove(conn->out.data, conn->out.data + sent, total - sent); 1001 | conn->out.used -= sent; 1002 | } 1003 | 1004 | if(conn->sending_from_fd && conn->out.used == 0) 1005 | { 1006 | /* Tell the kernel to use sendfile */ 1007 | do 1008 | { 1009 | ssize_t n = sendfile(conn->fd, conn->file_fd, NULL, 1010 | conn->file_len - conn->file_off); 1011 | 1012 | if(n < 0) 1013 | { 1014 | if(errno == EAGAIN) 1015 | // Would block, we're done. 1016 | break; 1017 | 1018 | // An error occurred. 1019 | return 0; 1020 | } 1021 | 1022 | conn->file_off += n; 1023 | } 1024 | while(conn->file_off < conn->file_len); 1025 | 1026 | if(conn->file_off == conn->file_len) 1027 | { 1028 | // Done sending the file. 1029 | (void) close(conn->file_fd); 1030 | conn->sending_from_fd = 0; 1031 | } 1032 | } 1033 | return 1; 1034 | } 1035 | 1036 | static uint32_t find(const char *str, uint32_t len, const char *seq) 1037 | { 1038 | if(seq == NULL || seq[0] == '\0') 1039 | return UINT32_MAX; 1040 | 1041 | if(str == NULL || len == 0) 1042 | return UINT32_MAX; 1043 | 1044 | uint32_t i = 0, seqlen = strlen(seq); 1045 | while(1) 1046 | { 1047 | while(i < len && str[i] != seq[0]) 1048 | i += 1; 1049 | 1050 | if(i == len) 1051 | return UINT32_MAX; 1052 | 1053 | assert(str[i] == seq[0]); 1054 | 1055 | if(i > len - seqlen) 1056 | return UINT32_MAX; 1057 | 1058 | if(!strncmp(seq, str + i, seqlen)) 1059 | return i; 1060 | 1061 | i += 1; 1062 | } 1063 | } 1064 | 1065 | static void append_string_to_output_buffer(conn_t *conn, xh_string data) 1066 | { 1067 | if(conn->sending_from_fd) 1068 | conn->failed_to_append = 1; 1069 | 1070 | if(conn->failed_to_append) 1071 | return; 1072 | 1073 | if(conn->out.size - conn->out.used < (uint32_t) data.len) 1074 | { 1075 | uint32_t new_size = 2 * conn->out.size; 1076 | 1077 | if(new_size < conn->out.used + (uint32_t) data.len) 1078 | new_size = conn->out.used + data.len; 1079 | 1080 | void *temp = realloc(conn->out.data, new_size); 1081 | 1082 | if(temp == NULL) 1083 | { 1084 | conn->failed_to_append = 1; 1085 | return; 1086 | } 1087 | 1088 | conn->out.data = temp; 1089 | conn->out.size = new_size; 1090 | } 1091 | 1092 | memcpy(conn->out.data + conn->out.used, data.str, data.len); 1093 | conn->out.used += data.len; 1094 | return; 1095 | } 1096 | 1097 | static bool client_wants_to_keep_alive(xh_request *req) 1098 | { 1099 | bool keep_alive; 1100 | 1101 | const char *h_connection = xh_header_get(req, "Connection"); 1102 | 1103 | if(h_connection == NULL) 1104 | // No [Connection] header. No keep-alive. 1105 | keep_alive = 0; 1106 | else 1107 | { 1108 | //remove leading spaces 1109 | h_connection += strspn(h_connection, " "); 1110 | 1111 | if(!strcasecmp(h_connection, "Keep-Alive")) 1112 | keep_alive = 1; 1113 | else /* "Close" or other case */ 1114 | keep_alive = 0; 1115 | } 1116 | 1117 | return keep_alive; 1118 | } 1119 | 1120 | static bool server_wants_to_keep_alive(context_t *ctx, conn_t *conn) 1121 | { 1122 | bool keep_alive; 1123 | 1124 | if(conn->served >= 20) 1125 | keep_alive = 0; 1126 | 1127 | if(ctx->connum > 0.6 * ctx->maxconns) 1128 | keep_alive = 0; 1129 | 1130 | return keep_alive; 1131 | } 1132 | 1133 | static void append_response_status_line_to_output_buffer(conn_t *conn, int status) 1134 | { 1135 | char buffer[256]; 1136 | 1137 | const char *status_text = statis_code_to_status_text(status); 1138 | assert(status_text != NULL); 1139 | 1140 | int n = snprintf(buffer, sizeof(buffer), "HTTP/1.1 %d %s\r\n", 1141 | status, status_text); 1142 | assert(n >= 0); 1143 | 1144 | if((unsigned int) n > sizeof(buffer)-1) 1145 | n = sizeof(buffer)-1; 1146 | 1147 | append_string_to_output_buffer(conn, xh_string_new(buffer, n)); 1148 | } 1149 | 1150 | static void append_response_head_to_output_buffer(xh_response *res, conn_t *conn) 1151 | { 1152 | append_response_status_line_to_output_buffer(conn, res->status); 1153 | for(int i = 0; i < res->headers.count; i += 1) 1154 | { 1155 | xh_pair header = res->headers.list[i]; 1156 | append_string_to_output_buffer(conn, header.key); 1157 | append_string_to_output_buffer(conn, xh_string_from_literal(": ")); 1158 | append_string_to_output_buffer(conn, header.val); 1159 | append_string_to_output_buffer(conn, xh_string_from_literal("\r\n")); 1160 | } 1161 | append_string_to_output_buffer(conn, xh_string_from_literal("\r\n")); 1162 | } 1163 | 1164 | static enum { 1165 | ORF_OK, ORF_FORBIDDEN, 1166 | ORF_NOTFOUND, ORF_OTHER, 1167 | } open_regular_file(const char *file, int *size, int *fd) 1168 | { 1169 | assert(fd != NULL); 1170 | 1171 | *fd = open(file, O_RDONLY); 1172 | if(*fd < 0) { 1173 | switch(errno) { 1174 | case EACCES: return ORF_FORBIDDEN; 1175 | case ENOENT: return ORF_NOTFOUND; 1176 | } 1177 | return ORF_OTHER; 1178 | } 1179 | 1180 | struct stat buf; 1181 | 1182 | if(fstat(*fd, &buf)) 1183 | { close(*fd); return ORF_OTHER; } 1184 | 1185 | if(!S_ISREG(buf.st_mode)) // Path doesn't refer to a regular file. 1186 | { close(*fd); return ORF_OTHER; } 1187 | 1188 | if(size) 1189 | *size = buf.st_size; 1190 | return ORF_OK; 1191 | } 1192 | 1193 | static void generate_response_by_calling_the_callback(context_t *ctx, conn_t *conn) 1194 | { 1195 | xh_request *req = &conn->request.public; 1196 | 1197 | // If it's a HEAD request, tell the callback that 1198 | // it's a GET request but then throw awaiy the body. 1199 | bool head_only = 0; 1200 | if(req->method_id == XH_HEAD) 1201 | { 1202 | head_only = 1; 1203 | req->method_id = XH_GET; 1204 | req->method = xh_string_from_literal("GET"); 1205 | } 1206 | 1207 | xh_response2 res2; 1208 | xh_response *res = &res2.public; 1209 | { 1210 | res_init(&res2); 1211 | 1212 | ctx->callback(req, res, ctx->userp); 1213 | 1214 | req_deinit(req); 1215 | 1216 | if(res2.failed) 1217 | { 1218 | /* Callback failed to build the response. 1219 | * Overwrite with a new error response. 1220 | */ 1221 | res_reinit(&res2); 1222 | res->status = 500; 1223 | } 1224 | } 1225 | 1226 | /* Determine Content-Length and, if the * 1227 | * response body was specified with a * 1228 | * file name, open the file. */ 1229 | 1230 | int content_length = -1, // Initialized these to shut up 1231 | file_fd = -1; // the compiler :S 1232 | 1233 | bool sending_file; 1234 | 1235 | if(res->file == NULL) 1236 | { 1237 | /* The callback specified the 1238 | response body with a string. */ 1239 | if(res->body.str == NULL) 1240 | res->body.str = ""; 1241 | 1242 | if(res->body.len < 0) 1243 | res->body.len = strlen(res->body.str); 1244 | 1245 | content_length = res->body.len; 1246 | sending_file = 0; 1247 | } 1248 | else 1249 | { 1250 | /* The callback specified the 1251 | response body as a file name. */ 1252 | 1253 | switch(open_regular_file(res->file, 1254 | &content_length, &file_fd)) 1255 | { 1256 | case ORF_FORBIDDEN: 1257 | res_reinit(&res2); 1258 | res->status = 403; 1259 | content_length = 0; 1260 | sending_file = 0; 1261 | break; 1262 | 1263 | case ORF_NOTFOUND: 1264 | res_reinit(&res2); 1265 | res->status = 404; 1266 | content_length = 0; 1267 | sending_file = 0; 1268 | break; 1269 | 1270 | case ORF_OTHER: 1271 | res_reinit(&res2); 1272 | res->status = 500; 1273 | content_length = 0; 1274 | sending_file = 0; 1275 | break; 1276 | 1277 | case ORF_OK: 1278 | assert(file_fd >= 0 && content_length >= 0); 1279 | sending_file = 1; 1280 | break; 1281 | 1282 | /* Don't add a [default] case to make 1283 | sure all return codes are handled. */ 1284 | } 1285 | } 1286 | 1287 | assert(content_length >= 0); 1288 | 1289 | bool callback_wants_to_keep_alive = !res->close; 1290 | bool keep_alive = client_wants_to_keep_alive(req) 1291 | && server_wants_to_keep_alive(ctx, conn) 1292 | && callback_wants_to_keep_alive; 1293 | 1294 | xh_header_add(res, "Content-Length", "%d", content_length); 1295 | xh_header_add(res, "Connection", keep_alive ? "Keep-Alive" : "Close"); 1296 | append_response_head_to_output_buffer(res, conn); 1297 | 1298 | /* Now write the body to the output or, if the * 1299 | * request was originally HEAD, throw the body * 1300 | * away. */ 1301 | 1302 | if(head_only == 1) 1303 | { 1304 | if(sending_file) 1305 | close(file_fd); 1306 | } 1307 | else 1308 | { 1309 | if(sending_file) 1310 | { 1311 | conn->file_fd = file_fd; 1312 | conn->file_off = 0; 1313 | conn->file_len = content_length; 1314 | conn->sending_from_fd = 1; 1315 | } 1316 | else append_string_to_output_buffer(conn, res->body); 1317 | } 1318 | 1319 | conn->served += 1; 1320 | 1321 | if(!keep_alive) 1322 | conn->close_when_uploaded = 1; 1323 | 1324 | res_deinit(&res2); 1325 | } 1326 | 1327 | static uint32_t determine_content_length(xh_request *req) 1328 | { 1329 | int i; 1330 | for(i = 0; i < req->headers.count; i += 1) 1331 | if(!strcasecmp(req->headers.list[i].key.str, 1332 | "Content-Length")) // TODO: Make it case-insensitive. 1333 | break; 1334 | 1335 | if(i == req->headers.count) 1336 | // No Content-Length header. 1337 | // Assume a length of 0. 1338 | return 0; 1339 | 1340 | const char *s = req->headers.list[i].val.str; 1341 | unsigned int k = 0; 1342 | 1343 | while(is_space(s[k])) 1344 | k += 1; 1345 | 1346 | if(s[k] == '\0') 1347 | // Header Content-Length is empty. 1348 | // Assume a length of 0. 1349 | return 0; 1350 | 1351 | if(!is_digit(s[k])) 1352 | // The first non-space character 1353 | // isn't a digit. That's bad. 1354 | return UINT32_MAX; 1355 | 1356 | uint32_t result = s[k] - '0'; 1357 | 1358 | k += 1; 1359 | 1360 | while(is_digit(s[k])) 1361 | { 1362 | result = result * 10 + s[k] - '0'; 1363 | k += 1; 1364 | } 1365 | 1366 | while(is_space(s[k])) 1367 | k += 1; 1368 | 1369 | if(s[k] != '\0') 1370 | // The header contains something other 1371 | // than whitespace and digits. Bad. 1372 | return UINT32_MAX; 1373 | 1374 | return result; 1375 | } 1376 | 1377 | static void when_data_is_ready_to_be_read(context_t *ctx, conn_t *conn) 1378 | { 1379 | // Download the data in the input buffer. 1380 | uint32_t downloaded; 1381 | { 1382 | buffer_t *b = &conn->in; 1383 | uint32_t before = b->used; 1384 | while(1) 1385 | { 1386 | if(b->size - b->used < 128) 1387 | { 1388 | uint32_t new_size = (b->size == 0) ? 512 : (2 * b->size); 1389 | 1390 | // NOTE: We allocate one extra byte because this 1391 | // way we're sure that any sub-string of the 1392 | // buffer can be safely made zero-terminated 1393 | // by writing a zero after it temporarily. 1394 | void *temp = realloc(b->data, new_size + 1); 1395 | 1396 | if(temp == NULL) 1397 | { 1398 | // ERROR! 1399 | close_connection(ctx, conn); 1400 | return; 1401 | } 1402 | 1403 | // TODO: Change the pointers in conn->request 1404 | // if the head was already parsed. 1405 | 1406 | b->data = temp; 1407 | b->size = new_size; 1408 | } 1409 | 1410 | assert(b->size > b->used); 1411 | 1412 | int n = recv(conn->fd, b->data + b->used, b->size - b->used, 0); 1413 | 1414 | if(n <= 0) 1415 | { 1416 | if(n == 0) 1417 | { 1418 | // Peer disconnected. 1419 | close_connection(ctx, conn); 1420 | return; 1421 | } 1422 | 1423 | if(errno == EAGAIN || errno == EWOULDBLOCK) 1424 | break; // Done downloading. 1425 | 1426 | // ERROR! 1427 | #if DEBUG 1428 | perror("recv"); 1429 | #endif 1430 | close_connection(ctx, conn); 1431 | return; 1432 | } 1433 | 1434 | b->used += n; 1435 | } 1436 | downloaded = b->used - before; 1437 | } 1438 | 1439 | int served_during_this_while_loop = 0; 1440 | 1441 | while(1) 1442 | { 1443 | if(!conn->head_received) 1444 | { 1445 | // Search for an \r\n\r\n. 1446 | uint32_t i; 1447 | { 1448 | uint32_t start = 0; 1449 | if(served_during_this_while_loop == 0 && conn->in.used > downloaded + 3) 1450 | start = conn->in.used - downloaded - 3; 1451 | 1452 | i = find(conn->in.data + start, conn->in.used - start, "\r\n\r\n"); 1453 | 1454 | if(i == UINT32_MAX) 1455 | // No \r\n\r\n found. The head of the request wasn't fully received yet. 1456 | return; 1457 | 1458 | // i is relative to start. 1459 | i += start; 1460 | } 1461 | 1462 | struct parse_err_t err = parse(conn->in.data, i+4, &conn->request.public); 1463 | 1464 | uint32_t len = 0; // Anything other than UINT32_MAX goes. 1465 | if(err.msg == NULL) 1466 | len = determine_content_length(&conn->request.public); // Returns UINT32_MAX on failure. 1467 | 1468 | if(err.msg != NULL || len == UINT32_MAX) 1469 | { 1470 | char buffer[512]; 1471 | if(len == UINT32_MAX) 1472 | { 1473 | static const char msg[] = "Couldn't determine the content length"; 1474 | (void) snprintf(buffer, sizeof(buffer), 1475 | "HTTP/1.1 400 Bad Request\r\n" 1476 | "Content-Type: text/plain;charset=utf-8\r\n" 1477 | "Content-Length: %ld\r\n" 1478 | "Connection: Close\r\n" 1479 | "\r\n%s", sizeof(msg)-1, msg); 1480 | } 1481 | else if(err.internal) 1482 | { 1483 | (void) snprintf(buffer, sizeof(buffer), 1484 | "HTTP/1.1 500 Internal Server Error\r\n" 1485 | "Content-Type: text/plain;charset=utf-8\r\n" 1486 | "Content-Length: %d\r\n" 1487 | "Connection: Close\r\n" 1488 | "\r\n%s", err.len, err.msg); 1489 | } 1490 | else 1491 | { 1492 | // 400 Bad Request. 1493 | (void) snprintf(buffer, sizeof(buffer), 1494 | "HTTP/1.1 400 Bad Request\r\n" 1495 | "Content-Type: text/plain;charset=utf-8\r\n" 1496 | "Content-Length: %d\r\n" 1497 | "Connection: Close\r\n" 1498 | "\r\n%s", err.len, err.msg); 1499 | } 1500 | 1501 | // NOTE: If the static buffer [buffer] is too small 1502 | // to hold the response then the response will 1503 | // be sent truncated. But that's not a problem 1504 | // since we'll close the connection after this 1505 | // response either way. 1506 | 1507 | append_string_to_output_buffer(conn, xh_string_new(buffer, -1)); 1508 | conn->close_when_uploaded = 1; 1509 | return; 1510 | } 1511 | 1512 | conn->head_received = 1; 1513 | conn->body_offset = i + 4; 1514 | conn->body_length = len; 1515 | } 1516 | 1517 | if(conn->head_received && conn->body_offset + conn->body_length <= conn->in.used) 1518 | { 1519 | /* The rest of the body arrived. */ 1520 | 1521 | // Make the body temporarily zero-terminated: get the byte 1522 | // that comes after the body, then overwrite it with a '\0'. 1523 | // When you don't need it to be zero-terminated anymore, 1524 | // put the saved byte back in. 1525 | 1526 | char first_byte_after_body_in_input_buffer 1527 | = conn->in.data[conn->body_offset + conn->body_length]; 1528 | 1529 | conn->in.data[conn->body_offset + conn->body_length] = '\0'; 1530 | 1531 | xh_request *req = &conn->request.public; 1532 | req->body = xh_string_new(conn->in.data + conn->body_offset, conn->body_length); 1533 | 1534 | generate_response_by_calling_the_callback(ctx, conn); 1535 | 1536 | // Restore the byte after the body. 1537 | conn->in.data[conn->body_offset + conn->body_length] 1538 | = first_byte_after_body_in_input_buffer; 1539 | 1540 | // Remove the request from the input buffer by 1541 | // copying back its remaining contents. 1542 | uint32_t consumed = conn->body_offset + conn->body_length; 1543 | memmove(conn->in.data, conn->in.data + consumed, conn->in.used - consumed); 1544 | conn->in.used -= consumed; 1545 | conn->head_received = 0; 1546 | 1547 | served_during_this_while_loop += 1; 1548 | 1549 | if(conn->close_when_uploaded) 1550 | break; 1551 | } 1552 | } 1553 | } 1554 | 1555 | void xh_quit(xh_handle handle) 1556 | { 1557 | context_t *ctx = handle; 1558 | ctx->exiting = 1; 1559 | } 1560 | 1561 | static const char *init(context_t *context, const char *addr, 1562 | unsigned short port, const xh_config *config) 1563 | { 1564 | if(config->maximum_parallel_connections == 0) 1565 | return "The number of maximum parallel connections isn't allowed to be 0"; 1566 | 1567 | if(config->backlog == 0) 1568 | return "The backlog isn't allowed to be 0"; 1569 | 1570 | { 1571 | context->fd = socket(AF_INET, SOCK_STREAM, 0); 1572 | 1573 | if(context->fd < 0) 1574 | return "Failed to create socket"; 1575 | 1576 | if(config->reuse_address) 1577 | { 1578 | int v = 1; 1579 | if(setsockopt(context->fd, SOL_SOCKET, 1580 | SO_REUSEADDR, &v, sizeof(v))) 1581 | { 1582 | (void) close(context->fd); 1583 | return "Failed to set socket option"; 1584 | } 1585 | } 1586 | 1587 | struct in_addr inp; 1588 | if(addr == NULL) 1589 | inp.s_addr = INADDR_ANY; 1590 | else 1591 | if(!inet_aton(addr, &inp)) 1592 | { 1593 | (void) close(context->fd); 1594 | return "Malformed IPv4 address"; 1595 | } 1596 | 1597 | struct sockaddr_in temp; 1598 | 1599 | memset(&temp, 0, sizeof(temp)); 1600 | 1601 | temp.sin_family = AF_INET; 1602 | temp.sin_port = htons(port); 1603 | temp.sin_addr = inp; 1604 | 1605 | if(bind(context->fd, (struct sockaddr*) &temp, sizeof(temp))) 1606 | { 1607 | (void) close(context->fd); 1608 | return "Failed to bind to address"; 1609 | } 1610 | 1611 | if(listen(context->fd, config->backlog)) 1612 | { 1613 | (void) close(context->fd); 1614 | return "Failed to listen for connections"; 1615 | } 1616 | } 1617 | 1618 | { 1619 | context->epfd = epoll_create1(0); 1620 | 1621 | if(context->epfd < 0) 1622 | { 1623 | (void) close(context->fd); 1624 | return "Failed to create epoll"; 1625 | } 1626 | 1627 | struct epoll_event temp; 1628 | 1629 | temp.events = EPOLLIN; 1630 | temp.data.ptr = NULL; 1631 | 1632 | if(epoll_ctl(context->epfd, EPOLL_CTL_ADD, context->fd, &temp)) 1633 | { 1634 | (void) close(context->fd); 1635 | (void) close(context->epfd); 1636 | return "Failed to add listener to epoll"; 1637 | } 1638 | } 1639 | 1640 | { 1641 | context->pool = malloc(config->maximum_parallel_connections * sizeof(conn_t)); 1642 | 1643 | if(context->pool == NULL) 1644 | { 1645 | (void) close(context->fd); 1646 | (void) close(context->epfd); 1647 | return "Failed to allocate connection pool"; 1648 | } 1649 | 1650 | for(unsigned int i = 0; i < config->maximum_parallel_connections; i += 1) 1651 | { 1652 | context->pool[i].fd = -1; 1653 | context->pool[i].next = context->pool + i + 1; 1654 | } 1655 | 1656 | context->pool[config->maximum_parallel_connections-1].next = NULL; 1657 | 1658 | context->freelist = context->pool; 1659 | } 1660 | 1661 | context->connum = 0; 1662 | context->maxconns = config->maximum_parallel_connections; 1663 | context->exiting = 0; 1664 | return NULL; 1665 | } 1666 | 1667 | xh_config xh_get_default_configs() 1668 | { 1669 | return (xh_config) { 1670 | .reuse_address = 1, 1671 | .maximum_parallel_connections = 512, 1672 | .backlog = 128, 1673 | }; 1674 | } 1675 | 1676 | const char *xhttp(const char *addr, unsigned short port, 1677 | xh_callback callback, void *userp, 1678 | xh_handle *handle, const xh_config *config) 1679 | { 1680 | xh_config dummy = xh_get_default_configs(); 1681 | if(config == NULL) 1682 | config = &dummy; 1683 | 1684 | context_t context; 1685 | 1686 | const char *error = init(&context, addr, port, config); 1687 | 1688 | if(error != NULL) 1689 | return error; 1690 | 1691 | context.callback = callback; 1692 | context.userp = userp; 1693 | 1694 | if(handle) 1695 | *handle = &context; 1696 | 1697 | struct epoll_event events[64]; 1698 | 1699 | while(!context.exiting) 1700 | { 1701 | int num = epoll_wait(context.epfd, events, sizeof(events)/sizeof(events[0]), 5000); 1702 | 1703 | for(int i = 0; i < num; i += 1) 1704 | { 1705 | if(events[i].data.ptr == NULL) 1706 | { 1707 | // New connection. 1708 | accept_connection(&context); 1709 | continue; 1710 | } 1711 | 1712 | conn_t *conn = events[i].data.ptr; 1713 | 1714 | if(events[i].events & EPOLLRDHUP) 1715 | { 1716 | // Disconnection. 1717 | close_connection(&context, conn); 1718 | continue; 1719 | } 1720 | 1721 | if(events[i].events & (EPOLLERR | EPOLLHUP)) 1722 | { 1723 | // Connection closed or an error occurred. 1724 | // We continue as nothing happened so that 1725 | // the error is reported on the [recv] or 1726 | // [send] call site. 1727 | events[i].events = EPOLLIN | EPOLLOUT; 1728 | } 1729 | 1730 | int old_connum = context.connum; 1731 | 1732 | if((events[i].events & (EPOLLIN | EPOLLPRI)) 1733 | && conn->close_when_uploaded == 0) 1734 | { 1735 | // Note that this may close the connection. If any logic 1736 | // were to come after this function, it couldn't refer 1737 | // to the connection structure. 1738 | when_data_is_ready_to_be_read(&context, conn); 1739 | } 1740 | 1741 | if(old_connum == context.connum) 1742 | { 1743 | // The connection wasn't closed. Try to 1744 | // upload the data in the output buffer. 1745 | 1746 | if(!upload(conn)) 1747 | 1748 | close_connection(&context, conn); 1749 | 1750 | else 1751 | if(conn->out.used == 0 && conn->close_when_uploaded) 1752 | close_connection(&context, conn); 1753 | } 1754 | } 1755 | } 1756 | 1757 | for(unsigned int i = 0; i < config->maximum_parallel_connections; i += 1) 1758 | if(context.pool[i].fd != -1) 1759 | close_connection(&context, context.pool + i); 1760 | 1761 | free(context.pool); 1762 | (void) close(context.fd); 1763 | (void) close(context.epfd); 1764 | return NULL; 1765 | } 1766 | 1767 | int xh_urlcmp(const char *URL, const char *fmt, ...) 1768 | { 1769 | va_list va; 1770 | va_start(va, fmt); 1771 | int res = xh_vurlcmp(URL, fmt, va); 1772 | va_end(va); 1773 | return res; 1774 | } 1775 | 1776 | /* Returns: 1777 | * 0 - Match 1778 | * 1 - No match 1779 | * -1 - Error 1780 | */ 1781 | int xh_vurlcmp(const char *URL, const char *fmt, va_list va) 1782 | { 1783 | #define MATCH 0 1784 | #define ERROR -1 1785 | #define NOMATCH 1 1786 | 1787 | long i = 0; // Cursor over [fmt] 1788 | long j = 0; // Cursor over [URL] 1789 | while(1) { 1790 | 1791 | while(fmt[i] != '\0' && fmt[i] != ':') { 1792 | 1793 | if(URL[j] != fmt[i]) 1794 | return NOMATCH; 1795 | 1796 | i += 1; 1797 | j += 1; 1798 | } 1799 | 1800 | if(fmt[i] == '\0' || URL[j] == '\0') 1801 | break; 1802 | 1803 | assert(URL[j] != '\0'); 1804 | assert(fmt[i] == ':'); 1805 | 1806 | i += 1; // Skip ':' 1807 | 1808 | if(fmt[i] == 'd') { 1809 | 1810 | if(!isdigit(URL[j])) 1811 | return NOMATCH; 1812 | 1813 | long long buff = 0; 1814 | 1815 | do { 1816 | 1817 | long d = (URL[j] - '0'); 1818 | 1819 | if(buff > (LLONG_MAX - d) / 10) 1820 | return ERROR; /* Overflow */ 1821 | 1822 | buff = buff * 10 + d; 1823 | 1824 | j += 1; 1825 | 1826 | } while(isdigit(URL[j])); 1827 | 1828 | long long *dst = va_arg(va, long long*); 1829 | if(dst != NULL) 1830 | *dst = buff; 1831 | 1832 | } else if(fmt[i] == 's') { 1833 | 1834 | long off = j; 1835 | while(URL[j] != '\0' && URL[j] != '/' && URL[j] != fmt[i+1]) 1836 | j += 1; 1837 | long len = j - off; 1838 | 1839 | long dst_len = va_arg(va, long); 1840 | char *dst_ptr = va_arg(va, char*); 1841 | 1842 | if(dst_ptr != NULL && dst_len > 0) { 1843 | long copy; 1844 | if(dst_len >= len+1) 1845 | copy = len; 1846 | else 1847 | copy = dst_len-1; 1848 | memcpy(dst_ptr, URL + off, copy); 1849 | dst_ptr[copy] = '\0'; 1850 | } 1851 | 1852 | } else 1853 | /* Format ended unexpectedly or 1854 | got an invalid format specifier. */ 1855 | return ERROR; 1856 | 1857 | i += 1; // Skip the 'd' or 's' 1858 | } 1859 | 1860 | /* If the program gets here it means that either 1861 | * [fmt] or [URL] ended. If that's the case, if 1862 | * the other didn't end, then there's no match. 1863 | */ 1864 | if(fmt[i] != '\0' || URL[j] != '\0') 1865 | return NOMATCH; 1866 | 1867 | return MATCH; 1868 | 1869 | #undef MATCH 1870 | #undef ERROR 1871 | #undef NOMATCH 1872 | } 1873 | -------------------------------------------------------------------------------- /xhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef XHTTP_H 2 | #define XHTTP_H 3 | 4 | typedef void *xh_handle; 5 | 6 | typedef struct { 7 | char *str; int len; 8 | } xh_string; 9 | 10 | typedef struct { 11 | xh_string key, val; 12 | } xh_pair; 13 | 14 | typedef struct { 15 | xh_pair *list; 16 | int count; 17 | } xh_table; 18 | 19 | typedef enum { 20 | XH_GET = 1, 21 | XH_HEAD = 2, 22 | XH_POST = 4, 23 | XH_PUT = 8, 24 | XH_DELETE = 16, 25 | XH_CONNECT = 32, 26 | XH_OPTIONS = 64, 27 | XH_TRACE = 128, 28 | XH_PATCH = 256, 29 | } xh_method; 30 | 31 | typedef struct { 32 | xh_method method_id; 33 | xh_string method; 34 | xh_string params; 35 | xh_string URL; 36 | unsigned int version_minor; 37 | unsigned int version_major; 38 | xh_table headers; 39 | xh_string body; 40 | } xh_request; 41 | 42 | typedef struct { 43 | 44 | int status; 45 | xh_table headers; 46 | xh_string body; 47 | const char *file; 48 | 49 | _Bool close; 50 | } xh_response; 51 | 52 | typedef struct { 53 | _Bool reuse_address; 54 | unsigned int maximum_parallel_connections; 55 | unsigned int backlog; 56 | } xh_config; 57 | 58 | typedef void (*xh_callback)(xh_request*, xh_response*, void*); 59 | 60 | const char *xhttp(const char *addr, unsigned short port, 61 | xh_callback callback, void *userp, 62 | xh_handle *handle, const xh_config *config); 63 | void xh_quit(xh_handle handle); 64 | xh_config xh_get_default_configs(); 65 | 66 | void xh_header_add(xh_response *res, const char *name, const char *valfmt, ...); 67 | void xh_header_rem(xh_response *res, const char *name); 68 | const char *xh_header_get(void *req_or_res, const char *name); 69 | _Bool xh_header_cmp(const char *a, const char *b); 70 | 71 | int xh_urlcmp(const char *URL, const char *fmt, ...); 72 | int xh_vurlcmp(const char *URL, const char *fmt, va_list va); 73 | 74 | 75 | #define xh_string_new(s, l) \ 76 | ((xh_string) { (s), ((int) (l)) < 0 ? (int) strlen(s) : (int) (l) }) 77 | 78 | #define xh_string_from_literal(s) \ 79 | ((xh_string) { (s), sizeof(s)-1 }) 80 | 81 | #endif // #ifndef XHTTP_H --------------------------------------------------------------------------------