├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── serve.c /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | serve 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Venkatraman Srikanth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -o serve serve.c 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) 2 | 3 | # micro-http 4 | 5 | A tiny static file server written in C, with functionality similar to Python's SimpleHTTPServer. Presents a file listing for directories. Handles multiple requests asynchronously. 6 | 7 | * Build by running `make` or `make serve`. 8 | 9 | * Add `serve` to your `$PATH`. 10 | 11 | * Run `serve 9000` to serve the current working directory. Visit localhost:9000. 12 | -------------------------------------------------------------------------------- /serve.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file serve.c 3 | * A minimal HTTP/1.1 static file server. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // Define a standard CRLF line ending 18 | #define EOL "\r\n" 19 | #define EOLSIZE 2 20 | 21 | // Define a standard buffer allocation size 22 | #define BUFSIZE 1024 23 | 24 | // Define a large buffer allocation size 25 | #define BIGBUFSIZE 1024 26 | 27 | // Define a number of headers 28 | #define HEADERCOUNT 50 29 | 30 | // Fix the locaation from which the files are served 31 | char WEBROOT[256]; 32 | 33 | /** 34 | * Structure to hold a header 35 | */ 36 | struct header_frame { 37 | char field[BUFSIZE]; 38 | char value[BUFSIZE]; 39 | }; 40 | 41 | /** 42 | * Structure to hold a request 43 | */ 44 | struct request_frame { 45 | // Request method - GET, POST, etc. 46 | char* method; 47 | // Location of the requested resource, like /home/index.html 48 | char* resource; 49 | // Protocol (Usually HTTP/1.1) 50 | char* protocol; 51 | // Body of the request 52 | char* body; 53 | // Number of headers 54 | int header_count; 55 | // A list of headers 56 | struct header_frame headers[HEADERCOUNT]; 57 | }; 58 | 59 | /** 60 | * Structure to hold a response 61 | */ 62 | struct response_frame { 63 | // Protocol (Usally HTTP/1.1) 64 | char* protocol; 65 | // Status code - 200, 400, etc 66 | int status_code; 67 | // Response message - OK, NOT FOUND, etc 68 | char* status_message; 69 | // Number of headers 70 | int header_count; 71 | // A list of response headers 72 | struct header_frame headers[HEADERCOUNT]; 73 | }; 74 | 75 | /** 76 | * Structure of mapping from an extension to a MIME type 77 | */ 78 | typedef struct { 79 | char *ext; 80 | char *mimetype; 81 | } extn; 82 | 83 | /** 84 | * A list of common file extensions and their MIME types 85 | * TODO: Extend this list 86 | */ 87 | extn extensions[] ={ 88 | {"aiff", "audio/x-aiff"}, 89 | {"avi", "video/avi"}, 90 | {"bin", "application/octet-stream"}, 91 | {"bmp", "image/bmp"}, 92 | {"css", "text/css"}, 93 | {"c", "text/x-c"}, 94 | {"doc", "application/msword"}, 95 | {"gif", "image/gif" }, 96 | {"gz", "image/gz" }, 97 | {"htmls", "text/html"}, 98 | {"html","text/html" }, 99 | {"html", "text/html"}, 100 | {"ico", "image/ico"}, 101 | {"jpeg","image/jpeg"}, 102 | {"jpg", "image/jpg" }, 103 | {"js", "application/x-javascript"}, 104 | {"mp3", "audio/mpeg3"}, 105 | {"mpeg", "video/mpeg"}, 106 | {"mpg", "video/mpeg"}, 107 | {"md", "text/markdown"}, 108 | {"pdf","application/pdf"}, 109 | {"php", "text/html" }, 110 | {"png", "image/png" }, 111 | {"png", "image/png"}, 112 | {"rar","application/octet-stream"}, 113 | {"tar", "image/tar" }, 114 | {"tiff", "image/tiff"}, 115 | {"txt", "text/plain" }, 116 | {"xml", "application/xml"}, 117 | {"zip", "application/zip"} 118 | }; 119 | 120 | /** 121 | * Helper function to throw an error 122 | * 123 | * @param[in] error_message error message to print before exit 124 | */ 125 | void error(const char *msg) { 126 | perror(msg); 127 | exit(1); 128 | } 129 | 130 | /** 131 | * Helper function to check if a given path points to a filename of a 132 | * directory. 133 | * 134 | * @param[in] path path to determine of file or directory 135 | */ 136 | int is_regular_file(const char *path) { 137 | struct stat path_stat; 138 | stat(path, &path_stat); 139 | return S_ISREG(path_stat.st_mode); 140 | } 141 | 142 | /** 143 | * A helper function to copy contents of one buffer into another 144 | * An alternative to strcpy() 145 | * 146 | * @param[in] srcbuffer buffer to create copy of 147 | * @param[in] len length of the buffer 148 | */ 149 | char* bufcpy(char* srcbuffer, size_t len) { 150 | char* p = malloc(len * sizeof(char)); 151 | memcpy(p, srcbuffer, len * sizeof(char)); 152 | return p; 153 | } 154 | 155 | /** 156 | * Helper function to get the extension, given the filename 157 | */ 158 | const char *get_filename_ext(const char *filename) { 159 | const char *dot = strrchr(filename, '.'); 160 | if(!dot || dot == filename) return ""; 161 | return dot + 1; 162 | } 163 | 164 | 165 | /** 166 | * Function to parse the HTTP request headers and body 167 | * Returns 1 if parsing successful 168 | * 169 | * @param[in] request request bytes 170 | * @param[out] request_frame struct output parsed request 171 | */ 172 | int parse_request(char* requestbuf, struct request_frame *request) { 173 | // Create a temporary buffer to copy the request into 174 | char *temprequest = malloc(BUFSIZE); 175 | temprequest = bufcpy(requestbuf, BUFSIZE); 176 | 177 | // Read the first line of the input, and split it into it's components 178 | // The format for the first request line as per RFC 7230 is - 179 | // [method] [request-target] [HTTP-version] CRLF 180 | char* request_save_buffer; 181 | request->method = strtok_r(temprequest, " \t\n", &request_save_buffer); 182 | request->resource = strtok_r(NULL, " \t", &request_save_buffer); 183 | request->protocol = strtok_r(NULL, " \t\n", &request_save_buffer); 184 | 185 | // Iterate through the remaining request headers. 186 | // Buffers that store the current header being read 187 | char* current_header; 188 | char* current_header_save_buffer; 189 | // Buffers that store the current header token being read 190 | char* current_token; 191 | char* current_token_save_buffer; 192 | 193 | // Store the headers as they're parsed 194 | struct header_frame tempheader; 195 | int header_count = 0; 196 | 197 | // Flag to check if parsing headers is complete 198 | int headers_complete_flag = 0; 199 | 200 | // Copy the request into a temp for manipulation 201 | temprequest = bufcpy(requestbuf, BUFSIZE); 202 | 203 | // Iterate over the headers 204 | for (;; temprequest = NULL) { 205 | // If all the headers have been parsed, only the request body is left 206 | if (headers_complete_flag) { 207 | request->body = current_header_save_buffer; 208 | break; 209 | } 210 | 211 | // Read the current line of input 212 | current_header = 213 | strtok_r(temprequest, "\n", ¤t_header_save_buffer); 214 | 215 | // Skip the first line. We've already parsed that 216 | // TODO: This is kinda ugly. Fix it. 217 | if (header_count == 0) { 218 | header_count++; 219 | continue; 220 | } 221 | 222 | // Split the header from the key:value pair 223 | // The format of a header as per RFC 7230 is - 224 | // [field-name]: [field-value] 225 | // Split the header key 226 | current_token 227 | = strtok_r(current_header, ":", ¤t_token_save_buffer); 228 | 229 | // If the headers are over, stop 230 | // Check for a NULL value or a trailing CRLF 231 | // HTTP Requests typically drop a CRLF (optional by RFC 7230) 232 | if (current_token == NULL 233 | || (strlen(current_token) == 1 && *current_token == EOL[0])) 234 | { 235 | headers_complete_flag = 1; 236 | continue; 237 | } 238 | strcpy(tempheader.field, current_token); 239 | 240 | // Split the header value 241 | current_token = strtok_r(NULL, "\n", ¤t_token_save_buffer); 242 | current_token_save_buffer = NULL; 243 | 244 | // If the header value has a leading space, remove it 245 | if(*current_token == ' ') { 246 | current_token++; 247 | } 248 | 249 | strcpy(tempheader.value, current_token); 250 | request->headers[header_count++] = tempheader; 251 | } 252 | 253 | request->header_count = header_count; 254 | 255 | free(temprequest); 256 | return 1; 257 | } 258 | 259 | /** 260 | * Takes a parsed request struct and prints it 261 | * 262 | * @param[in] request_frame 263 | */ 264 | void print_request_verbose(const struct request_frame *request) { 265 | // Pretty print the parsed request 266 | printf("--- REQUEST RECEIVED --- \n"); 267 | printf("\nRequest Method - %s", request->method); 268 | printf("\nRequest Resource - %s", request->resource); 269 | printf("\nRequest Protocol - %s", request->protocol); 270 | printf("\n\nHEADERS : \n"); 271 | 272 | for(int i = 1; i < request->header_count; ++i) { 273 | printf("Header : %s\n", request->headers[i].field); 274 | printf("Value : %s\n\n", request->headers[i].value); 275 | } 276 | 277 | printf("Body : %s\n", request->body); 278 | printf("--- REQUEST COMPLETE ---\n\n"); 279 | } 280 | 281 | /** 282 | * Takes a parsed request struct and prints it (sparse) 283 | * 284 | * @param[in] request_frame 285 | */ 286 | void print_request_sparse(const struct request_frame *request) { 287 | printf("\n%s %s %s", 288 | request->method, request->resource, request->protocol); 289 | } 290 | 291 | /** 292 | * Takes a parsed request and generates the response 293 | * 294 | * @param[in] request_frame the parsed request struct 295 | * @param[out] response_frame output parsed response struct 296 | */ 297 | void response_generator( 298 | const struct request_frame* req, 299 | struct response_frame* res 300 | ) { 301 | // Set the response protocol 302 | res->protocol = "HTTP/1.1"; 303 | 304 | // Initialize the number of headers 305 | res->header_count = 0; 306 | 307 | // Set the standard response unless otherwise 308 | res->status_code = 200; 309 | res->status_message = "OK"; 310 | 311 | // Check the request protocol field 312 | if( ! (strcmp(req->protocol,"HTTP/1.1") 313 | || strcmp(req->protocol,"HTTP/1.0")) ) { 314 | res->status_code = 400; 315 | strcpy(res->status_message, "Bad Request"); 316 | } 317 | 318 | // Determine the absolute path to the requested resource 319 | char* resource_path = req->resource; 320 | char* full_resource_path = malloc(BUFSIZE); 321 | strcpy(full_resource_path, WEBROOT); 322 | strcat(full_resource_path, resource_path); 323 | 324 | // Check if the requested resource is a file, or a directory 325 | if (is_regular_file(full_resource_path)) { 326 | // If it's a file, read the contents and serve the file 327 | 328 | // Find the file size, to set the Content-Lenth field 329 | FILE *fresource = fopen(full_resource_path, "rb"); 330 | fseek(fresource, 0L, SEEK_END); 331 | int file_size = ftell(fresource); 332 | fclose(fresource); 333 | 334 | // Find the extension of the file, and get it's MIME Type 335 | // TODO: Replace this with a hashtable for improved performance 336 | const char* this_extension; 337 | this_extension = get_filename_ext(full_resource_path); 338 | 339 | // Set the Content-Length header 340 | char content_length[20]; 341 | sprintf(content_length, "%d", file_size); 342 | // ^ Converting the integer into a string (hacky?) 343 | strcpy(res->headers[res->header_count].field, "Content-Length"); 344 | strcpy(res->headers[res->header_count].value, content_length); 345 | res->header_count++; 346 | 347 | // Set the content type header. Find this from the extension. 348 | // Iterate through the extensions list and break out when it's been 349 | // matched. Copy over the corresponding MIME type 350 | char mimetype[40]; 351 | int mime_set = 0; 352 | for (int i = 0; i < sizeof(extensions)/sizeof(extn); ++i) { 353 | if (!strcmp(extensions[i].ext, this_extension)) { 354 | mime_set = 1; 355 | strcpy(mimetype, extensions[i].mimetype); 356 | break; 357 | } 358 | } 359 | if (! mime_set) { 360 | strcpy(mimetype, "application/octet-stream"); 361 | } 362 | strcpy(res->headers[res->header_count].field, "Content-Type"); 363 | strcpy(res->headers[res->header_count].value, mimetype); 364 | res->header_count++; 365 | } 366 | free(full_resource_path); 367 | } 368 | 369 | /** 370 | * Function to send back response through socket 371 | * 372 | * param[in] sockfd the socket to send the response through 373 | * param[in] request_frame the request information object 374 | * param[in] response_frame the response information object 375 | */ 376 | void send_response( 377 | int sockfd, 378 | const struct request_frame* req, 379 | const struct response_frame* res 380 | ) { 381 | 382 | // Set the first status line 383 | char* header_line = malloc(BUFSIZE); 384 | sprintf( 385 | header_line, "%s %d %s\n", 386 | res->protocol, 387 | res->status_code, 388 | res->status_message 389 | ); 390 | write(sockfd, header_line, strlen(header_line)); 391 | 392 | // Write the headers 393 | // Iterate through the headers, sprintf them into the right format, store 394 | // them in the temp, and write them to the stream 395 | char* header = malloc(BUFSIZE); 396 | for(int i=0; i < res->header_count; ++i ) { 397 | sprintf( 398 | header, 399 | "%s: %s\n", 400 | res->headers[i].field, 401 | res->headers[i].value 402 | ); 403 | write(sockfd, header, strlen(header)); 404 | } 405 | 406 | // Write a newline to separate the headers and the body 407 | write(sockfd, "\n", 1); 408 | 409 | // Write the response body 410 | // Determine the absolute path to the requested resource 411 | char* resource_path = req->resource; 412 | char* full_resource_path = malloc(BUFSIZE); 413 | strcpy(full_resource_path, WEBROOT); 414 | strcat(full_resource_path, resource_path); 415 | 416 | // If it's not a regular file, it's a directory 417 | // Serve an index page with a file listing 418 | if (is_regular_file(full_resource_path)) { 419 | // Allocate and assign the main response body 420 | // This opens the requested resource and assigns it onto the body buffer 421 | char response_buffer[BIGBUFSIZE]; 422 | 423 | // Render the file 424 | FILE *fresource = fopen(full_resource_path, "rb"); 425 | int nread = 0; 426 | // Read the file in chunks, and write them to the stream 427 | if (fresource) { 428 | while(nread = fread(response_buffer, 1, BIGBUFSIZE, fresource)){ 429 | write(sockfd, response_buffer, sizeof(response_buffer)); 430 | } 431 | write(sockfd, EOL, EOLSIZE); 432 | } else { 433 | error("FILE INVALID!"); 434 | } 435 | fclose(fresource); 436 | } 437 | else { 438 | // It's a directory. Display a file listing. 439 | // Initialize directory check parameters 440 | DIR *directory; 441 | struct dirent *dir_struct; 442 | char current_line[BIGBUFSIZE]; 443 | char* dirtemp = malloc(BIGBUFSIZE); 444 | char* resource_path_ptr = malloc(BIGBUFSIZE); 445 | directory = opendir(full_resource_path); 446 | 447 | // Begin rendering the HTML for the file listing 448 | strcpy(current_line, "

File Listing

    "); 449 | write(sockfd, current_line, strlen(current_line)); 450 | if (directory) { 451 | resource_path_ptr = resource_path; 452 | if (!strcmp(resource_path_ptr, "/")) { 453 | strcpy(resource_path_ptr, ""); 454 | } 455 | while(*resource_path_ptr == '/') { 456 | resource_path_ptr++; 457 | } 458 | while ((dir_struct = readdir(directory)) != NULL) { 459 | strcpy(dirtemp, dir_struct->d_name); 460 | if (!strcmp(dirtemp, ".") 461 | || !strcmp(dirtemp, "..")) { 462 | continue; 463 | } 464 | while(*dirtemp == '/') { 465 | dirtemp++; 466 | } 467 | if(!strlen(resource_path_ptr)) { 468 | sprintf(current_line, "
  • %s
  • ", resource_path_ptr, dirtemp, dirtemp); 469 | } 470 | else { 471 | sprintf(current_line, "
  • %s
  • ", resource_path_ptr, dirtemp, dirtemp); 472 | 473 | } 474 | write(sockfd, current_line, strlen(current_line)); 475 | } 476 | closedir(directory); 477 | } 478 | strcpy(current_line, "
"); 479 | write(sockfd, current_line, strlen(current_line)); 480 | } 481 | 482 | free(header); 483 | free(header_line); 484 | free(full_resource_path); 485 | } 486 | 487 | /** 488 | * HTTP Handler 489 | * Handles a single request. 490 | * 491 | * @param[in] sockfd socket to write response to 492 | */ 493 | int handler(int sockfd) { 494 | // Allocate buffers to read the request 495 | char *request = malloc(BUFSIZE); 496 | struct request_frame req; 497 | struct response_frame res; 498 | 499 | // Read request from socket 500 | recv(sockfd, request, BUFSIZE, 0); 501 | 502 | // Parse the request and print to stdout 503 | if (parse_request(request, &req)) { 504 | // print_request_verbose(&req); 505 | print_request_sparse(&req); 506 | } 507 | 508 | // Populate the response struct 509 | response_generator(&req, &res); 510 | send_response(sockfd, &req, &res); 511 | 512 | return 1; 513 | } 514 | 515 | /** 516 | * Opens a socket on the HTTP listen port and listens for connections 517 | */ 518 | int main(int argc, char* argv[]) { 519 | // If the user does not specify a port, point out application usage 520 | if (argc < 2) { 521 | fprintf(stderr, "usage : server port [webroot]\n"); 522 | return 1; 523 | } 524 | // Useful for debugging. Flushes stdout, without any buffering 525 | setbuf(stdout, NULL); 526 | 527 | strcpy(WEBROOT, getenv("PWD")); 528 | 529 | // Create a socket 530 | int sockfd, respsockfd; 531 | socklen_t addrlen; 532 | struct sockaddr_in address; 533 | 534 | // Check for successful socket initialization 535 | if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) <= 0){ 536 | printf("Socket creation failed"); 537 | } 538 | 539 | // Set binding parameters 540 | int port = atoi(argv[1]); 541 | 542 | address.sin_family = AF_INET; 543 | address.sin_addr.s_addr = INADDR_ANY; 544 | address.sin_port = htons(port); 545 | 546 | // Bind socket to address and port 547 | if (bind(sockfd, (struct sockaddr *) &address, sizeof(address)) == 0){ 548 | printf("Server started! Listening on port %d\n", port); 549 | } else { 550 | error("Error opening connection\n"); 551 | } 552 | 553 | // Begin listen loop 554 | while (1) { 555 | // Check for incoming client connections 556 | if (listen(sockfd, 10) < 0) { 557 | perror("server: listen"); 558 | exit(1); 559 | } 560 | if ((respsockfd 561 | = accept(sockfd, (struct sockaddr *) &address, &addrlen)) < 0) 562 | { 563 | perror("server: accept"); 564 | exit(1); 565 | } 566 | 567 | // Spawns a child process which handles the request 568 | int pid = fork(); 569 | if (pid < 0) { 570 | error("Could not initialize response process\n"); 571 | } 572 | 573 | // If child process, close the request socket, and initiate the 574 | // handler 575 | if (pid == 0) { 576 | close(sockfd); 577 | handler(respsockfd); 578 | exit(0); 579 | } 580 | // If parent process, close the response socket 581 | else { 582 | close(respsockfd); 583 | } 584 | } 585 | close(sockfd); 586 | return 0; 587 | } 588 | --------------------------------------------------------------------------------