├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bin └── httpd.yaml └── src ├── CMakeLists.txt ├── cache.c ├── cache.h ├── config.c ├── config.h ├── connection.c ├── connection.h ├── http.c ├── http.h ├── logger.c ├── logger.h ├── main.c ├── master.c ├── master.h ├── siphash.h ├── types.c ├── types.h ├── worker.c └── worker.h /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | cmake-build-debug/ 3 | web/ 4 | bin/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | dist: xenial # required for cmake v3.12 4 | 5 | script: 6 | - cmake . 7 | - make install 8 | - echo TODO Run tests 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(CMAKE_C_STANDARD 11) 4 | set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) 5 | set(LEAR_INSTALL_DIR ${PROJECT_SOURCE_DIR}/bin) 6 | set(RUNTIME_OUTPUT_DIRECTORY ${LEAR_INSTALL_DIR}) 7 | 8 | add_compile_options(-Wall -std=c11 -O3) 9 | 10 | add_subdirectory(src) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michał Bień 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LEAR - Linux Engine for Asset Retrieval 2 | 3 | [![Build Status](https://travis-ci.org/Glorf/lear.svg?branch=master)](https://travis-ci.org/Glorf/lear) 4 | 5 | ## Description 6 | LEAR is a simple HTTP server designed to be as simple and fast as possible to achieve one task: 7 | serve static resources with amazing efficiency. Currently the project is in its early stage, 8 | but is gaining momentum and features. 9 | ## Architecture 10 | While being small and lightweight, LEAR is as concurrent and non-blocking as possible. 11 | It also uses state-of-the-art technologies and solutions to achieve its task: serve your assets 12 | rapidly. It features: 13 | * Multiprocess architecture of traffic handler, using Linux >3.9's `SO_REUSEPORT` 14 | * Epoll queue for each worker 15 | * Fully non-blocking architecture of network IO with dynamically allocated read and write buffers 16 | * Lots of customization through user-friendly YAML properties file 17 | ## Features 18 | * LEAR accomplishes its task by implementing GET, HEAD, and OPTIONS methods of HTTP/1.1 19 | * Its non-blocking nature and concurrence-by-design makes responses incredibly fast and processing very efficient 20 | * Server implements the most common response status codes and offers response body customization (eg. custom 404 error pages for error verbosity and SPA routers) 21 | * Server parses headers properly and returns Content-Length with any request 22 | * Requested resources are mmapes, so LEAR has fast direct access to them 23 | * Custom string format and lack of standard C null-terminated string makes server safe from memory retrieval attacks 24 | 25 | ## Installation 26 | * Prerequisites: CMake, GCC, libyaml 27 | * Installation 28 | 29 | 30 | ```bash 31 | $ git clone https://github.com/Glorf/lear.git 32 | $ cd lear 33 | $ cmake . 34 | $ make install 35 | $ cd bin 36 | # now modify httpd.yaml to suit your needs 37 | $ ./lear 38 | 39 | ## Customization 40 | httpd.yaml offers you all the options currently available - there are no console switches. 41 | I believe that at this moment these settings are self-explanatory. We'll do full configs 42 | rewrite soon, config documentation is planned to appear afterwards. 43 | 44 | ## FAQ 45 | #### Why is LEAR faster than nginx/apache... 46 | Because LEAR is small and simple. LEAR does one task - serve static pages - and does it well. 47 | It also implements only a small subset of the HTTP, which makes it incomplete in the sense 48 | of being standard-compilant, but also very fast in the sense of real life use. 49 | #### You said it's fast but actually it's slow on big files 50 | LEAR caching support is WIP. When it's ready, we hope it'll outperform most common 51 | HTTP engines 52 | #### Why is X unsupported? 53 | Because LEAR started in October 2018 - so it's quite a young project isn't it? If you like C, 54 | please help us in development by accomplishing some task from the 55 | [Github Issues page](https://github.com/Glorf/lear/issues). If you prefer not to 56 | - just be patient. 57 | #### Should I use it in my production environment? 58 | **No.** In its current status, LEAR is extremely incomplete, even for simplest deployments. 59 | Its security has also not yet been checked by any means. Please, keep us in mind and return 60 | in few months - we're sure LEAR will be your next production server then. 61 | 62 | ## Benchmark 63 | As development is in early stage, this benchmark is just a performance profiling tool 64 | for me, and maybe significant information for people who like this project. It will 65 | be updated recently when any performance-related changes happen. Logging is currently 66 | disabled in LEAR while benchmarking. Also, please, do not believe these benchmarks. 67 | It's just the result of some code run on my laptop, running default, non-tuned nginx, 68 | you know. If you have any results to share, please, submit a PR to this readme! 69 | 70 | #### ApacheBench 2.3 results 71 | 72 | | Method | Keep-alive? | File size [B] | Number of requests | Concurrency level | LEAR master [rps] | NGINX 1.5.15 [rps] | 73 | |--------|-------------|---------------|--------------------|-------------------|-------------------|--------------------| 74 | | GET | Yes | 865 | 1000000 | 100 | **98885.12** | 62859.55 | 75 | | GET | Yes | 865 | 1000000 | 1 | **27786.05** | 21591.06 | 76 | | GET | Yes | 2229306 | 10000 | 100 | 760.80 | **1821.60** | 77 | | GET | Yes | 2229306 | 10000 | 1 | 781.60 | **1508.20** | 78 | 79 | ## TODO 80 | If you like this project, feel free to contribute, fork and send PRs! Current, non-finished 81 | list of feature requests is available on Github Issues page. Remember to keep straight KISS rule - LEAR is 82 | never going to be RFC-complete as it's designed just to serve GET responses as fast 83 | as possible. 84 | 85 | Please, open an issue if you find any bugs or consider any feature that fits the spirit of 86 | this project. 87 | 88 | ## License 89 | LEAR is distributed for free as a source code, under the permissive MIT license. 90 | -------------------------------------------------------------------------------- /bin/httpd.yaml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 3 | queueSize: 128 4 | maxNumWorkers: 5 5 | listenPort: 9000 6 | requestBlockSize: 512 7 | maxRequestSize: 5192 8 | maxResponseSize: 819200 9 | maxURILength: 128 10 | requestTimeout: 30 11 | host: 12 | name: "127.0.0.1" 13 | webDir: /home/mbien/Projekty/lear/web 14 | notFound: "/a" 15 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(lear C) 3 | 4 | add_executable(lear main.c master.c master.h worker.c worker.h connection.c connection.h config.c config.h logger.c logger.h http.c http.h cache.c cache.h types.h types.c siphash.h) 5 | target_link_libraries(lear yaml) 6 | install(TARGETS lear DESTINATION ${LEAR_INSTALL_DIR}) 7 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include "cache.h" 2 | #include "logger.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | s_string read_file(s_string filename) { 13 | s_string filecontent; 14 | filecontent.length = 0; 15 | filecontent.position = NULL; 16 | 17 | char *str_filename = to_c_string(&filename); 18 | 19 | int fd = open(str_filename, O_RDONLY); 20 | free(str_filename); 21 | 22 | if(fd < 0) { 23 | message_log("Failed to open file", ERR); 24 | return filecontent; 25 | } 26 | 27 | struct stat s; 28 | fstat(fd, &s); 29 | filecontent.length = (size_t)s.st_size; 30 | 31 | filecontent.position = mmap(NULL, filecontent.length, PROT_READ, MAP_SHARED, fd, 0); 32 | 33 | if(filecontent.position < 0) { 34 | message_log("Error while mapping file", ERR); 35 | } 36 | 37 | close(fd); 38 | 39 | return filecontent; 40 | } 41 | 42 | int is_directory(s_string path) { 43 | int res = 0; 44 | char *str_path = to_c_string(&path); 45 | struct stat statbuf; 46 | res = stat(str_path, &statbuf); 47 | free(str_path); 48 | 49 | if (res != 0) 50 | return 0; 51 | return S_ISDIR(statbuf.st_mode); 52 | } 53 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef PUTHTTPD_CACHE_H 2 | #define PUTHTTPD_CACHE_H 3 | 4 | #include "types.h" 5 | 6 | s_string read_file(s_string filename); 7 | int is_directory(s_string path); 8 | 9 | #endif //PUTHTTPD_CACHE_H 10 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "types.h" 3 | 4 | #include 5 | #include 6 | 7 | 8 | struct map { 9 | char key[64]; 10 | char value[64]; 11 | }; 12 | 13 | typedef struct map s_map; 14 | 15 | s_map config[40]; 16 | int config_num; 17 | 18 | s_global_config *global_config; 19 | 20 | int init_config(char path[]) { 21 | yaml_parser_t parser; 22 | yaml_event_t event; 23 | FILE *file = fopen(path, "rb"); 24 | 25 | if(file == NULL) { 26 | perror("Failed to open config file"); 27 | return -1; 28 | } 29 | 30 | yaml_parser_initialize(&parser); 31 | yaml_parser_set_input_file(&parser, file); 32 | 33 | config_num = 0; 34 | 35 | typedef enum {KEY, VALUE} yaml_state; 36 | yaml_state state = KEY; 37 | char alias[64] = {'\0'}; 38 | char key[64] = {'\0'}; 39 | 40 | do { 41 | if(!yaml_parser_parse(&parser, &event)) { 42 | perror("Error while parsing YAML"); 43 | break; 44 | } 45 | switch(event.type) { 46 | case YAML_ALIAS_EVENT: 47 | case YAML_SEQUENCE_START_EVENT: 48 | case YAML_SEQUENCE_END_EVENT: 49 | case YAML_STREAM_START_EVENT: 50 | case YAML_DOCUMENT_START_EVENT: 51 | case YAML_DOCUMENT_END_EVENT: 52 | case YAML_STREAM_END_EVENT: 53 | case YAML_NO_EVENT: 54 | continue; 55 | case YAML_SCALAR_EVENT: 56 | if (state == KEY) { 57 | strcpy(key, (char *) (event.data.scalar.value)); 58 | state = VALUE; 59 | } else if (state == VALUE) { 60 | strcpy(config[config_num].key, alias); 61 | strcat(config[config_num].key, key); 62 | strcpy(config[config_num].value, (char *) (event.data.scalar.value)); 63 | state = KEY; 64 | config_num++; 65 | } 66 | break; 67 | case YAML_MAPPING_START_EVENT: 68 | if(state == VALUE) { 69 | strcpy(alias, key); 70 | strcat(alias, "."); 71 | state = KEY; 72 | } 73 | break; 74 | case YAML_MAPPING_END_EVENT: 75 | alias[0] = '\0'; 76 | //TODO: add deeper indentations 77 | break; 78 | } 79 | } while(event.type != YAML_STREAM_END_EVENT); 80 | yaml_event_delete(&event); 81 | 82 | 83 | yaml_parser_delete(&parser); 84 | fclose(file); 85 | 86 | init_global_config(); 87 | 88 | return config_num; 89 | } 90 | 91 | s_string read_config_string(char key[], char defaults[]) { 92 | s_string result; 93 | result.length = 0; 94 | for(int i=0; imax_URI_length = read_config_long("maxURILength", "128"); 126 | global_config->max_request_size = read_config_long("maxRequestSize", "3072"); 127 | global_config->max_block_size = read_config_long("requestBlockSize", "512"); 128 | global_config->request_timeout_sec = read_config_long("requestTimeout", "30"); 129 | 130 | //TODO: Move this to dynamic hosts map 131 | global_config->host.name = read_config_string("host.name", "localhost"); 132 | global_config->host.root_path = read_config_string("host.webDir", "/home/mbien/Projekty/lear/web"); 133 | global_config->host.not_found_path = read_config_string("host.notFound", "/404.html"); 134 | } -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by mbien on 08.11.18. 3 | // 4 | 5 | #ifndef PUTHTTPD_CONFIG_H 6 | #define PUTHTTPD_CONFIG_H 7 | 8 | #include "types.h" 9 | 10 | typedef struct { 11 | s_string name; 12 | s_string root_path; 13 | s_string not_found_path; 14 | } s_host; 15 | 16 | typedef struct { 17 | long max_URI_length; 18 | long max_request_size; 19 | long max_block_size; 20 | long request_timeout_sec; 21 | //TODO: Handle multiple hosts! 22 | s_host host; 23 | } s_global_config; 24 | 25 | 26 | 27 | int init_config(char path[]); 28 | s_string read_config_string(char key[], char defaults[]); 29 | long read_config_long(char key[], char defaults[]); 30 | s_global_config *get_global_config(); 31 | void init_global_config(); 32 | 33 | #endif //PUTHTTPD_CONFIG_H 34 | -------------------------------------------------------------------------------- /src/connection.c: -------------------------------------------------------------------------------- 1 | #include "connection.h" 2 | #include "config.h" 3 | #include "logger.h" 4 | #include "http.h" 5 | #include "worker.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | const char C_END_REQUEST[] = "\r\n\r\n"; 18 | 19 | int accept_client_connection(s_tcp_server *srv_in, int epoll_fd) { 20 | if(srv_in->status != RUNNING) { 21 | message_log("Failed to assign connection to server in non-running state", ERR); 22 | return -1; 23 | } 24 | int cli_socket = accept(srv_in->srv_socket, NULL, NULL); 25 | if(cli_socket < 0) { 26 | if(errno == EAGAIN || errno == EWOULDBLOCK) { 27 | message_log("Failed to initialize connection to any new client", DEBUG); 28 | return -1; 29 | } 30 | else { 31 | message_log("Failed to accept incoming connection", ERR); 32 | return -1; 33 | } 34 | } 35 | 36 | if(make_socket_nonblocking(cli_socket) < 0) { 37 | message_log("Failed to make client connection nonblocking", ERR); 38 | return -1; 39 | } 40 | 41 | struct epoll_event event; 42 | event.events = EPOLLIN | EPOLLOUT | EPOLLET; 43 | s_connection *connection = malloc(sizeof(s_connection)); 44 | connection->fd = cli_socket; 45 | 46 | connection->request_buffer = initialize_buffer(); 47 | connection->response_buffer = initialize_buffer(); 48 | 49 | connection->currentRequest = NULL; 50 | connection->requestQueue = 0; 51 | 52 | connection->next = NULL; 53 | 54 | connection->drop_timeout = time(NULL) + get_global_config()->request_timeout_sec; 55 | connection->prev = latest; 56 | if(latest != NULL) 57 | latest->next = connection; 58 | latest = connection; 59 | if(oldest == NULL) 60 | oldest = connection; 61 | 62 | event.data.ptr = connection; 63 | 64 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cli_socket, &event) < 0) { 65 | message_log("Epoll error adding accepted connection", ERR); 66 | return -1; 67 | } 68 | message_log("New connection processed.", DEBUG); 69 | 70 | return 0; 71 | } 72 | 73 | long read_client_connection(s_connection* cli_socket) { 74 | message_log("Request read started", INFO); 75 | 76 | ssize_t sum_transmitted = 0; 77 | ssize_t count = 0; 78 | while(count != -1) { 79 | if (cli_socket->request_buffer.size <= cli_socket->request_buffer.offset + 1) { //No space left 80 | if (cli_socket->request_buffer.size >= get_global_config()->max_request_size) { //request size limit exceeded! BAD REQUEST 81 | message_log("BAD REQUEST! - is too big", ERR); 82 | return -1; 83 | } else { //limit not exceeded yet, expand buffer 84 | expand_buffer(&cli_socket->request_buffer, get_global_config()->max_block_size); 85 | } 86 | } 87 | 88 | count = read(cli_socket->fd, 89 | cli_socket->request_buffer.payload + cli_socket->request_buffer.offset, 90 | cli_socket->request_buffer.size - cli_socket->request_buffer.offset); 91 | 92 | if (count == -1 && errno != EAGAIN) { 93 | message_log("Error while reading from client", ERR); 94 | return -1; 95 | } else if(count == -1 && errno == EAGAIN) { 96 | message_log("Finished this part of request", INFO); 97 | break; 98 | } else if(count == 0) { //Client disconnected 99 | return 0; 100 | } 101 | 102 | //Data was read 103 | cli_socket->request_buffer.offset += count; 104 | sum_transmitted += count; 105 | message_log("Data was read", INFO); 106 | 107 | } 108 | 109 | s_string bufferString; 110 | bufferString.length = cli_socket->request_buffer.offset; 111 | bufferString.position = cli_socket->request_buffer.payload; 112 | 113 | s_string bareRequest = substring(&bufferString, C_END_REQUEST); 114 | 115 | long offset = 0; 116 | while (bareRequest.position != NULL){ 117 | bareRequest.length += 2; //let's save first \r\n for header parsing purposes 118 | s_http_request *request = parse_request(&bareRequest); 119 | 120 | if(cli_socket->currentRequest == NULL) cli_socket->currentRequest = request; 121 | else { 122 | s_http_request *last; 123 | for (last = cli_socket->currentRequest; last->next != NULL; last = last->next); //get last element of queue 124 | last->next = request; //append new request at the end of queue 125 | } 126 | cli_socket->requestQueue++; 127 | 128 | offset += bareRequest.length+2; 129 | 130 | bufferString.length = cli_socket->request_buffer.offset - offset; 131 | bufferString.position = cli_socket->request_buffer.payload + offset; 132 | 133 | bareRequest = substring(&bufferString, C_END_REQUEST); 134 | } 135 | 136 | 137 | //Copy unused part of buffer that potentially holds beggining of next request 138 | cli_socket->request_buffer.size = cli_socket->request_buffer.offset - offset; //shrink buffer to as small as possible 139 | if(cli_socket->request_buffer.size > 0) { 140 | char *newbuf = malloc(cli_socket->request_buffer.size); 141 | memcpy(newbuf, cli_socket->request_buffer.payload + offset, 142 | cli_socket->request_buffer.size); //copy existing buffer 143 | free(cli_socket->request_buffer.payload); //free old buffer 144 | cli_socket->request_buffer.payload = newbuf; //reassign 145 | cli_socket->request_buffer.offset = cli_socket->request_buffer.size; 146 | } 147 | else { 148 | free(cli_socket->request_buffer.payload); 149 | cli_socket->request_buffer.offset = 0; 150 | } 151 | 152 | return sum_transmitted; 153 | } 154 | 155 | long write_client_connection(s_connection *cli_socket) { 156 | message_log("Response write started", DEBUG); 157 | 158 | ssize_t sum_transmitted = 0; 159 | ssize_t count = 0; 160 | while(count != -1) { 161 | count = write(cli_socket->fd, 162 | cli_socket->response_buffer.payload + cli_socket->response_buffer.offset, 163 | cli_socket->response_buffer.size - cli_socket->response_buffer.offset); 164 | 165 | if (count == -1) { 166 | switch(errno) { 167 | case EAGAIN: 168 | message_log("Client cannot accept any more packets, finishing", INFO); 169 | break; 170 | case ECONNRESET: 171 | message_log("Client aborted connection", INFO); 172 | return -1; 173 | default: 174 | message_log("Error while writing to client", ERR); 175 | return -1; 176 | } 177 | } 178 | 179 | //Data was written 180 | if(count > 0) { 181 | cli_socket->response_buffer.offset += count; 182 | sum_transmitted += count; 183 | message_log("Data written", INFO); 184 | } 185 | 186 | 187 | 188 | if(cli_socket->response_buffer.offset >= cli_socket->response_buffer.size) { 189 | message_log("Buffer is empty, finishing write", INFO); 190 | //Free buffer if empty 191 | free(cli_socket->response_buffer.payload); 192 | cli_socket->response_buffer.size = 0; 193 | cli_socket->response_buffer.offset = 0; 194 | return sum_transmitted; 195 | } 196 | } 197 | 198 | //Copy part of buffer that was not written yet 199 | cli_socket->response_buffer.size = 200 | cli_socket->response_buffer.size - cli_socket->response_buffer.offset; //shrink buffer to as small as possible 201 | char *newbuf = malloc(cli_socket->response_buffer.size); 202 | memcpy(newbuf, cli_socket->response_buffer.payload + cli_socket->response_buffer.offset, 203 | cli_socket->response_buffer.size); //copy existing buffer 204 | free(cli_socket->response_buffer.payload); //free old buffer 205 | cli_socket->response_buffer.payload = newbuf; //reassign 206 | cli_socket->response_buffer.offset = 0; 207 | 208 | 209 | return sum_transmitted; 210 | } 211 | 212 | int process_client_connection(s_connection *cli_socket){ 213 | message_log("Response being processed", INFO); 214 | while(cli_socket->requestQueue > 0 && cli_socket->currentRequest != NULL) { 215 | s_http_request *request = cli_socket->currentRequest; 216 | 217 | s_http_response response; 218 | response.body_length = 0; 219 | response.status = OK; 220 | response.headers_last = NULL; 221 | response.headers_first = NULL; 222 | 223 | if(process_http_request(cli_socket->currentRequest, &response) < 0) { //Main request processing thread 224 | message_log("Failed to produce response", ERR); 225 | response.status = INTERNAL_ERROR; 226 | } 227 | 228 | message_log("Request fullfilled", INFO); 229 | 230 | s_string headerString = generate_bare_header(&response); 231 | if(headerString.length <= 0) { 232 | message_log("Failed to serialize server response", ERR); 233 | return -1; 234 | } 235 | 236 | /*if(responseString.length > read_config_int("maxResponseSize", "81920000")) { 237 | message_log("Requested file is too big", ERR); 238 | //TODO: Should return 500 239 | 240 | return -1; 241 | }*/ 242 | 243 | //Reshape buffer to fit the data 244 | long offset = cli_socket->response_buffer.size; 245 | long payload_size = headerString.length + response.body_length; 246 | expand_buffer(&cli_socket->response_buffer, payload_size); 247 | 248 | //Copy new data to buffer 249 | memcpy(cli_socket->response_buffer.payload+offset, 250 | headerString.position, headerString.length); 251 | 252 | if(request->method == GET && response.body_length > 0) { 253 | offset += headerString.length; 254 | memcpy(cli_socket->response_buffer.payload + offset, 255 | response.body, response.body_length); 256 | 257 | munmap(response.body, response.body_length); 258 | } 259 | 260 | cli_socket->currentRequest = request->next; //detach request and free it 261 | cli_socket->requestQueue--; 262 | delete_request(request); 263 | 264 | //Try to write instantly - if impossible, leave in buffer 265 | write_client_connection(cli_socket); 266 | 267 | delete_string(&headerString); 268 | 269 | clear_string_list(response.headers_first); 270 | } 271 | 272 | return 0; 273 | } 274 | 275 | int close_client_connection(s_connection *cli_socket) { 276 | int res = close(cli_socket->fd); 277 | if(res < 0) { 278 | message_log("Failed to close connection to client", ERR); 279 | return -1; 280 | } 281 | 282 | detach_client_connection(cli_socket); 283 | 284 | for(s_http_request *curr = cli_socket->currentRequest; curr != NULL; ) { 285 | s_http_request *prev = curr; 286 | curr = prev->next; 287 | delete_request(prev); 288 | } 289 | 290 | clean_buffer(&cli_socket->request_buffer); 291 | clean_buffer(&cli_socket->response_buffer); 292 | 293 | free(cli_socket); 294 | 295 | return 0; 296 | } 297 | 298 | void detach_client_connection(s_connection *cli_socket) { 299 | if(cli_socket == latest || cli_socket == oldest) { 300 | if (cli_socket == latest) { 301 | latest = cli_socket->prev; 302 | if (latest != NULL) 303 | latest->next = NULL; 304 | } 305 | if (cli_socket == oldest) { 306 | oldest = cli_socket->next; 307 | if (oldest != NULL) 308 | oldest->prev = NULL; 309 | } 310 | } 311 | else { 312 | cli_socket->next->prev = cli_socket->prev; 313 | cli_socket->prev->next = cli_socket->next; 314 | } 315 | } 316 | 317 | void create_server_struct(s_tcp_server *srv_out) { 318 | srv_out->status = UNINITIALIZED; 319 | srv_out->srv_socket = -1; 320 | } 321 | 322 | int make_socket_nonblocking(int fd) { 323 | int flags = fcntl(fd, F_GETFL, 0); 324 | if (flags == -1) { 325 | message_log("Failed to get socket flags", ERR); 326 | return -1; 327 | } 328 | 329 | flags |= O_NONBLOCK; 330 | int s = fcntl(fd, F_SETFL, flags); 331 | if (s == -1) { 332 | message_log("Failed to set socket flags", ERR); 333 | return -1; 334 | } 335 | 336 | return 0; 337 | } 338 | 339 | int bind_server_socket(unsigned short port, s_tcp_server *srv_out) { 340 | //Set socket 341 | srv_out->srv_socket = socket(AF_INET, SOCK_STREAM, 0); 342 | if(srv_out->srv_socket < 0) { 343 | message_log("Failed to initialize server socket", ERR); 344 | return -1; 345 | } 346 | 347 | //Set socket properties - use new linux 3.9 API to distribute TCP connections 348 | //https://lwn.net/Articles/542629/ 349 | int opt = 1; 350 | if(setsockopt(srv_out->srv_socket, SOL_SOCKET, SO_REUSEPORT, (const char *)&opt, sizeof(opt)) < 0) { 351 | message_log("Failed to set sockopt!", ERR); 352 | return -1; 353 | } 354 | 355 | struct sockaddr_in server_address; 356 | memset(&server_address, 0, sizeof(struct sockaddr)); 357 | 358 | server_address.sin_family = AF_INET; 359 | server_address.sin_addr.s_addr = htonl(INADDR_ANY); 360 | server_address.sin_port = htons(port); 361 | 362 | //Bind socket 363 | int err; 364 | err = bind(srv_out->srv_socket, (struct sockaddr*)&server_address, sizeof(struct sockaddr)); 365 | if(err < 0) { 366 | message_log("Bind attempt failed", ERR); 367 | return -1; 368 | } 369 | 370 | make_socket_nonblocking(srv_out->srv_socket); 371 | 372 | //Listen on non-blocking socket 373 | err = listen(srv_out->srv_socket, (int)read_config_long("queueSize", "5")); 374 | if(err < 0) { 375 | message_log("Error while listening on port", ERR); 376 | return -1; 377 | } 378 | 379 | srv_out->status = RUNNING; 380 | 381 | return 0; 382 | } 383 | 384 | int close_server_socket(s_tcp_server *srv_in) { 385 | int res = close(srv_in->srv_socket); 386 | if(res < 0) { 387 | message_log("Failed to close server socket", ERR); 388 | srv_in->status = FAILURE; 389 | return -1; 390 | } 391 | 392 | srv_in->status = DOWN; 393 | 394 | return 0; 395 | } -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by mbien on 08.11.18. 3 | // 4 | 5 | #ifndef PUTHTTPD_CONNECTION_H 6 | #define PUTHTTPD_CONNECTION_H 7 | 8 | #include "http.h" 9 | 10 | typedef enum { 11 | UNINITIALIZED, 12 | FAILURE, 13 | RUNNING, 14 | DOWN 15 | } e_server_status; 16 | 17 | typedef struct { 18 | int srv_socket; 19 | e_server_status status; 20 | } s_tcp_server; 21 | 22 | typedef struct s_connection s_connection; 23 | 24 | struct s_connection { 25 | int fd; 26 | long drop_timeout; //time when timeout should be triggered 27 | s_buffer request_buffer; 28 | s_http_request *currentRequest; 29 | int requestQueue; 30 | s_buffer response_buffer; 31 | s_connection *next; 32 | s_connection *prev; 33 | }; 34 | 35 | int accept_client_connection(s_tcp_server *srv_in, int epoll_fd); 36 | long read_client_connection(s_connection *cli_socket); 37 | long write_client_connection(s_connection *cli_socket); 38 | int process_client_connection(s_connection *cli_socket); 39 | int bind_server_socket(unsigned short port, s_tcp_server *srv_out); 40 | void create_server_struct(s_tcp_server *srv_out); 41 | int make_socket_nonblocking(int fd); 42 | int close_client_connection(s_connection *cli_socket); 43 | void detach_client_connection(s_connection *cli_socket); 44 | int close_server_socket(s_tcp_server *srv_in); 45 | 46 | #endif //PUTHTTPD_CONNECTION_H -------------------------------------------------------------------------------- /src/http.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "logger.h" 3 | #include "config.h" 4 | #include "cache.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static const char *C_ENDLINE = "\r\n"; 12 | static const char *C_GET = "GET"; 13 | static const char *C_HEAD = "HEAD"; 14 | static const char *C_OPTIONS = "OPTIONS"; 15 | static const char *C_SPACE = " "; 16 | static const char *C_HEADER_STOPPER = ":"; 17 | 18 | static const char *C_HTTP[2] = {"HTTP/1.0", "HTTP/1.1"}; 19 | 20 | static const char *C_INDEX = "/index.html"; 21 | 22 | static const char *header_OK = "200 OK"; 23 | static const char *header_BAD_REQUEST = "400 Bad Request"; 24 | static const char *header_NOT_FOUND = "404 Not Found"; 25 | static const char *header_REQUEST_TIMEOUT = "408 Request Timeout"; 26 | static const char *header_REQUEST_TOO_LARGE = "413 Request Entity Too Large"; 27 | static const char *header_URI_TOO_LONG = "414 Request-URI Too Long"; 28 | static const char *header_INTERNAL_ERROR = "500 Internal Server Error"; 29 | static const char *header_NOT_IMPLEMENTED = "501 Not Implemented"; 30 | static const char *header_HTTP_VERSION_NOT_SUPPORTED = "505 HTTP Version Not Supported"; 31 | 32 | s_http_request *parse_request(s_string *bareRequest) { 33 | s_http_request *request = malloc(sizeof(s_http_request)); 34 | request->method = UNKNOWN; 35 | request->status = OK; 36 | request->headers_first = NULL; 37 | request->headers_last = NULL; 38 | request->next = NULL; 39 | request->hostname.position = NULL; 40 | request->resource.position = NULL; 41 | 42 | s_string line = substring(bareRequest, C_ENDLINE); 43 | 44 | long offset = 0; 45 | while(line.position != NULL) { 46 | //attach the stopper 47 | line.length += 2; 48 | parse_request_line(&line, request); 49 | if(request->status != OK) break; //request failed anyway, no need to process it 50 | //detach the parsed head 51 | offset += line.length; 52 | line.position = bareRequest->position+offset; 53 | line.length = bareRequest->length - offset; 54 | 55 | line = substring(&line, C_ENDLINE); 56 | } 57 | 58 | return request; 59 | } 60 | 61 | void parse_request_line(s_string *bareLine, s_http_request *request) { 62 | s_string offset; 63 | offset.length = bareLine->length; 64 | offset.position = bareLine->position; 65 | if(request->method == UNKNOWN) { //no data yet, it's first line 66 | s_string type = substring(&offset, C_SPACE); 67 | if(type.position == NULL) { 68 | request->status = BAD_REQUEST; 69 | return; 70 | } 71 | 72 | 73 | if(compare_string_const(&type, C_GET)) request->method = GET; 74 | else if(compare_string_const(&type, C_HEAD)) request->method = HEAD; 75 | else if(compare_string_const(&type, C_OPTIONS)) request->method = OPTIONS; 76 | else { 77 | message_log("Request method unsupported", INFO); 78 | string_log(&type, INFO); 79 | request->status = NOT_IMPLEMENTED; 80 | return; 81 | } 82 | 83 | offset.position += type.length + 1; //move to next part of line 84 | offset.length -= (type.length + 1); 85 | 86 | s_string resource = substring(&offset, C_SPACE); 87 | if(resource.position == NULL) { 88 | message_log("Error while parsing request header", ERR); 89 | request->status = BAD_REQUEST; 90 | return; 91 | } 92 | 93 | long max_uri = get_global_config()->max_URI_length; 94 | if(resource.length > max_uri) { 95 | message_log("Requested uri too long!", ERR); 96 | request->status = URI_TOO_LONG; 97 | return; 98 | } 99 | 100 | //resource path traversal security check 101 | //TODO: Perform full path traversal security check!! 102 | request->resource = create_string(resource.position, resource.length); 103 | if(request->resource.length == 0) { 104 | request->status = BAD_REQUEST; 105 | return; 106 | } 107 | else { 108 | unsigned long tmp_offset = 0; 109 | s_string tmp_str = substring(&resource, "/"); 110 | while(tmp_str.position != NULL) { 111 | 112 | if(tmp_str.length == 2 && compare_string_const(&tmp_str, "..")) { //path traversal! 113 | message_log("Possible path traversal!", WARN); 114 | request->status = BAD_REQUEST; 115 | break; 116 | } 117 | else if(tmp_str.length == 1 && compare_string_const(&tmp_str, ".")) { //suspicious current directory access? 118 | message_log("Possible path traversal!", WARN); 119 | request->status = BAD_REQUEST; 120 | break; 121 | } 122 | 123 | tmp_offset += tmp_str.length + 1; 124 | 125 | tmp_str.position = resource.position + tmp_offset; 126 | tmp_str.length = resource.length - tmp_offset; 127 | 128 | tmp_str = substring(&tmp_str, "/"); 129 | } 130 | if(tmp_str.length > 0) { //there are no more slashes, but some characters were not verified yet 131 | tmp_str.position = resource.position + tmp_offset; 132 | 133 | //TODO: Duplicate path traversal detection! Treat it in one method somehow 134 | if(tmp_str.length == 2 && compare_string_const(&tmp_str, "..")) { //path traversal! 135 | message_log("Possible path traversal!", WARN); 136 | request->status = BAD_REQUEST; 137 | } 138 | else if(tmp_str.length == 1 && compare_string_const(&tmp_str, ".")) { //suspicious current directory access? 139 | message_log("Possible path traversal!", WARN); 140 | request->status = BAD_REQUEST; 141 | } 142 | } 143 | } 144 | 145 | 146 | offset.position += resource.length + 1; 147 | offset.length -= (resource.length + 1); 148 | 149 | s_string protocol = substring(&offset, C_ENDLINE); 150 | if(protocol.position == NULL) { 151 | message_log("Bad protocol", DEBUG); 152 | request->status = BAD_REQUEST; 153 | return; 154 | } 155 | 156 | if(compare_string_const(&protocol, C_HTTP[1])) { 157 | request->version = V1_1; 158 | } 159 | else if(compare_string_const(&protocol, C_HTTP[0])) { 160 | request->version = V1_0; 161 | } 162 | else { 163 | message_log("HTTP version unsupported", DEBUG); 164 | request->status = HTTP_VERSION_NOT_SUPPORTED; 165 | } 166 | } 167 | else { //headers parsing 168 | s_string key = substring(&offset, C_HEADER_STOPPER); 169 | offset.position += key.length + 1; 170 | offset.length -= (key.length + 1); 171 | if (key.position == NULL) { 172 | request->status = BAD_REQUEST; 173 | return; 174 | } 175 | 176 | //Let's parse spaces if they are after header name 177 | while (offset.position[0] == ' ') { 178 | offset.position++; 179 | offset.length--; 180 | } 181 | 182 | s_string value = substring(&offset, C_ENDLINE); 183 | if (value.position == NULL) { 184 | request->status = BAD_REQUEST; 185 | return; 186 | } 187 | 188 | //TODO: delegate duplicate to function 189 | if(request->headers_first == NULL) { 190 | request->headers_first = malloc(sizeof(s_string_list)); 191 | request->headers_last = request->headers_first; 192 | } 193 | else { 194 | request->headers_last->next = malloc(sizeof(s_string_list)); 195 | request->headers_last = request->headers_last->next; 196 | } 197 | 198 | request->headers_last->next = NULL; 199 | request->headers_last->key = create_string(key.position, key.length); 200 | request->headers_last->value = create_string(value.position, value.length); 201 | } 202 | } 203 | 204 | int process_http_request(s_http_request *request, s_http_response *response) { 205 | 206 | response->version = request->version; 207 | 208 | if(request->status != OK) { 209 | //Sending error-like, body-less response 210 | response->status = request->status; 211 | response->body_length = 0; 212 | } 213 | else if(request->method == OPTIONS && compare_string_const(&request->resource, "*")) //it's not a file but OPTIONS query 214 | response->status = OK; 215 | else { 216 | string_log(&request->resource, INFO); 217 | 218 | //TODO: Read it from map & add multiple hosts 219 | 220 | s_string resource_dir = concat_string(get_global_config()->host.root_path, request->resource); 221 | 222 | 223 | if (is_directory(resource_dir)) { //if it's directory, look for index.html inside 224 | s_string indexDir = concat_string_const(resource_dir, C_INDEX); 225 | 226 | delete_string(&resource_dir); 227 | 228 | resource_dir = indexDir; 229 | } 230 | 231 | char *str_resource_dir = to_c_string(&resource_dir); 232 | if (access(str_resource_dir, F_OK) == -1) { //File not exist 233 | delete_string(&resource_dir); 234 | 235 | resource_dir = concat_string(get_global_config()->host.root_path, get_global_config()->host.not_found_path); 236 | 237 | message_log("Not found!", WARN); 238 | string_log(&resource_dir, WARN); 239 | 240 | response->status = NOT_FOUND; 241 | } else { 242 | response->status = OK; 243 | } 244 | free(str_resource_dir); 245 | 246 | string_log(&resource_dir, DEBUG); 247 | 248 | 249 | if(request->method == GET || request->method == HEAD) { //we don't have to retrieve resource for OPTIONS 250 | s_string page = read_file(resource_dir); 251 | response->body = page.position; 252 | response->body_length = (size_t) page.length; 253 | } 254 | 255 | if (response->body_length == -1) { 256 | message_log(resource_dir.position, WARN); 257 | message_log("Error while reading file", ERR); 258 | response->status = INTERNAL_ERROR; 259 | } 260 | 261 | delete_string(&resource_dir); 262 | } 263 | 264 | //Process basic headers 265 | for(s_string_list *header = request->headers_first; header != NULL; ) { 266 | string_log(&header->value, DEBUG); 267 | if(request->version==V1_0 && compare_string_const(&header->key, "Connection") && compare_string_const(&header->value, "Keep-Alive")) { //send keepalive 268 | //TODO: Duplicate - pass this and the one below to function 269 | if(response->headers_first == NULL) { 270 | response->headers_first = malloc(sizeof(s_string_list)); 271 | response->headers_last = response->headers_first; 272 | } 273 | else { 274 | response->headers_last->next = malloc(sizeof(s_string_list)); 275 | response->headers_last = response->headers_last->next; 276 | } 277 | //We transmit header as-is 278 | response->headers_last->value = create_string(header->value.position, header->value.length); 279 | response->headers_last->key = create_string(header->key.position, header->key.length); 280 | response->headers_last->next = NULL; 281 | message_log("Keepalive processed", DEBUG); 282 | } 283 | 284 | header = header->next; 285 | } 286 | 287 | if(request->method == OPTIONS && (response->status==OK || compare_string_const(&request->resource, "*"))) { //Global options query, return default 288 | //TODO: duplicate! 289 | if(response->headers_first == NULL) { 290 | response->headers_first = malloc(sizeof(s_string_list)); 291 | response->headers_last = response->headers_first; 292 | } 293 | else { 294 | response->headers_last->next = malloc(sizeof(s_string_list)); 295 | response->headers_last = response->headers_last->next; 296 | } 297 | 298 | response->headers_last->key = create_string("Allow", 14); 299 | response->headers_last->value = create_string("GET, HEAD, OPTIONS", 18); 300 | string_log(&response->headers_last->key, DEBUG); 301 | } 302 | 303 | //Message has body - we should transmit its length 304 | if(response->headers_first == NULL) { 305 | response->headers_first = malloc(sizeof(s_string_list)); 306 | response->headers_last = response->headers_first; 307 | } 308 | else { 309 | response->headers_last->next = malloc(sizeof(s_string_list)); 310 | response->headers_last = response->headers_last->next; 311 | } 312 | 313 | response->headers_last->key = create_string("Content-Length", 14); 314 | char number[11]; //to surely fit long 315 | sprintf(number, "%lu", response->body_length); 316 | response->headers_last->value = create_string(number, strlen(number)); 317 | response->headers_last->next = NULL; 318 | message_log("ContentLength added", DEBUG); 319 | string_log(&response->headers_last->key, DEBUG); 320 | 321 | return 0; 322 | } 323 | 324 | s_string generate_bare_header(s_http_response *response) { 325 | s_string result; 326 | 327 | result.length = 0; 328 | result.position = NULL; 329 | 330 | const char *protocol = C_HTTP[response->version]; 331 | 332 | switch(response->status) { 333 | case OK: 334 | forge_status_line(protocol, header_OK, response->headers_first, &result); 335 | return result; 336 | case BAD_REQUEST: 337 | forge_status_line(protocol, header_BAD_REQUEST, response->headers_first, &result); 338 | return result; 339 | case NOT_FOUND: 340 | forge_status_line(protocol, header_NOT_FOUND, response->headers_first, &result); 341 | return result; 342 | case REQUEST_TIMEOUT: 343 | forge_status_line(protocol, header_REQUEST_TIMEOUT, response->headers_first, &result); 344 | return result; 345 | case REQUEST_TOO_LARGE: 346 | forge_status_line(protocol, header_REQUEST_TOO_LARGE, response->headers_first, &result); 347 | return result; 348 | case URI_TOO_LONG: 349 | forge_status_line(protocol, header_URI_TOO_LONG, response->headers_first, &result); 350 | return result; 351 | case INTERNAL_ERROR: 352 | forge_status_line(protocol, header_INTERNAL_ERROR, response->headers_first, &result); 353 | return result; 354 | case NOT_IMPLEMENTED: 355 | default: 356 | forge_status_line(protocol, header_NOT_IMPLEMENTED, response->headers_first, &result); 357 | return result; 358 | case HTTP_VERSION_NOT_SUPPORTED: 359 | forge_status_line(protocol, header_HTTP_VERSION_NOT_SUPPORTED, response->headers_first, &result); 360 | return result; 361 | } 362 | } 363 | 364 | void forge_status_line(const char protocol[], const char status[], s_string_list *headers, s_string *result) { 365 | //TODO: consider hardcoded request header size so we won't have to count it as below 366 | unsigned long stat_len = strlen(status); 367 | result->length = 8 /*HTTP/1.[0,1]*/ + 1 /* */ + stat_len /*200 OK*/ + 2 /*\r\n*/; 368 | for(s_string_list *header = headers; header != NULL; ) { 369 | result->length += header->key.length + 2 + header->value.length + 2; 370 | 371 | header = header->next; 372 | } 373 | result->length += 2; //\r\n 374 | 375 | result->position = malloc(result->length); 376 | memcpy(result->position, protocol, 8); //TODO: move protocol to s_string to avoid these hardcodings! 377 | memcpy(result->position+8, " ", 1); 378 | memcpy(result->position+9, status, stat_len); //TODO: move status to s_string to avoid these hardcodings! 379 | memcpy(result->position+9+stat_len, "\r\n", 2); 380 | unsigned long offset = 11 + stat_len; 381 | for(s_string_list *header = headers; header != NULL; ) { 382 | memcpy(result->position + offset, header->key.position, header->key.length); 383 | offset += header->key.length; 384 | memcpy(result->position + offset, ": ", 2); 385 | offset += 2; 386 | memcpy(result->position + offset, header->value.position, header->value.length); 387 | offset += header->value.length; 388 | string_log(&header->value, DEBUG); 389 | memcpy(result->position + offset, "\r\n", 2); 390 | offset += 2; 391 | 392 | header = header->next; 393 | } 394 | 395 | memcpy(result->position + offset, "\r\n", 2); //end response header 396 | 397 | string_log(result, DEBUG); 398 | } 399 | 400 | void delete_request(s_http_request *request) { 401 | clear_string_list(request->headers_first); 402 | delete_string(&request->hostname); 403 | delete_string(&request->resource); 404 | free(request); 405 | } -------------------------------------------------------------------------------- /src/http.h: -------------------------------------------------------------------------------- 1 | #ifndef PUTHTTPD_HTTP_H 2 | #define PUTHTTPD_HTTP_H 3 | 4 | #include "types.h" 5 | 6 | typedef enum { 7 | OPTIONS, 8 | GET, 9 | HEAD, 10 | UNKNOWN, 11 | } e_http_methods; 12 | 13 | typedef enum { 14 | OK = 200, 15 | BAD_REQUEST = 400, 16 | NOT_FOUND = 404, 17 | REQUEST_TIMEOUT = 408, //TODO: handle it somehow 18 | REQUEST_TOO_LARGE = 413, 19 | URI_TOO_LONG = 414, 20 | INTERNAL_ERROR = 500, 21 | NOT_IMPLEMENTED = 501, 22 | HTTP_VERSION_NOT_SUPPORTED = 505 23 | } e_http_status; 24 | 25 | typedef enum { 26 | V1_0 = 0, 27 | V1_1 = 1 28 | } e_http_version; 29 | 30 | typedef struct { 31 | e_http_methods method; 32 | e_http_version version; 33 | s_string hostname; 34 | s_string_list *headers_first; 35 | s_string_list *headers_last; //we have both pointers so that we won't have to iterate while parsing 36 | s_string resource; 37 | e_http_status status; 38 | void *next; 39 | } s_http_request; 40 | 41 | typedef struct { 42 | e_http_status status; 43 | e_http_version version; 44 | s_string_list *headers_first; 45 | s_string_list *headers_last; //we have both pointers so that we won't have to iterate while parsing 46 | unsigned long body_length; 47 | char *body; 48 | } s_http_response; 49 | 50 | s_http_request *parse_request(s_string *bareRequest); 51 | void parse_request_line(s_string *bareLine, s_http_request *request); 52 | int process_http_request(s_http_request *request, s_http_response *response); 53 | s_string generate_bare_header(s_http_response *response); 54 | void forge_status_line(const char protocol[], const char status[], s_string_list *headers, s_string *result); 55 | void delete_request(s_http_request *request); 56 | 57 | #endif //PUTHTTPD_HTTP_H 58 | -------------------------------------------------------------------------------- /src/logger.c: -------------------------------------------------------------------------------- 1 | #include "logger.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | enum LogLevel runVerbosity; 9 | 10 | const char *str_levels[4] = {"DEBUG", "INFO", "WARN", "ERR"}; 11 | 12 | int init_logger(enum LogLevel logVerbosity) { 13 | runVerbosity = logVerbosity; 14 | return 0; 15 | } 16 | 17 | void message_log(char message[], enum LogLevel level) { 18 | if(level >= runVerbosity) { 19 | //POSIX claims fprintf is thread safe, added no mutex though 20 | if(level < ERR) fprintf(stdout, "[%s] %s\n", str_levels[level], message); 21 | else fprintf(stderr, "[%s] %s: %s\n", str_levels[level], message, strerror(errno)); 22 | } 23 | } 24 | 25 | void string_log(s_string *message, enum LogLevel level) { 26 | if(level >= runVerbosity) { 27 | //POSIX claims fprintf is thread safe, added no mutex though 28 | if(level < ERR) fprintf(stdout, "[%s] %.*s\n", str_levels[level], (int)message->length, message->position); 29 | else fprintf(stderr, "[%s] %.*s: %s\n", str_levels[level], (int)message->length, message->position, strerror(errno)); 30 | } 31 | } -------------------------------------------------------------------------------- /src/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef PUTHTTPD_LOGGER_H 2 | #define PUTHTTPD_LOGGER_H 3 | 4 | #include "types.h" 5 | 6 | enum LogLevel { 7 | DEBUG = 0, 8 | INFO = 1, 9 | WARN = 2, 10 | ERR = 3 11 | }; 12 | 13 | 14 | int init_logger(enum LogLevel logVerbosity); 15 | void message_log(char message[], enum LogLevel level); 16 | void string_log(s_string *message, enum LogLevel level); 17 | 18 | #endif //PUTHTTPD_LOGGER_H 19 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "master.h" 2 | #include "config.h" 3 | #include "logger.h" 4 | 5 | #include 6 | 7 | int main() { 8 | init_config("httpd.yaml"); //TODO: process log with pipes to master process to omit processing slowdowns 9 | init_logger(read_config_long("logging.level", "0")); 10 | message_log("Welcome to LEAR!", DEBUG); 11 | run_master(); 12 | 13 | } -------------------------------------------------------------------------------- /src/master.c: -------------------------------------------------------------------------------- 1 | #include "master.h" 2 | #include "config.h" 3 | #include "worker.h" 4 | #include "logger.h" 5 | 6 | #include 7 | #include 8 | 9 | struct worker { 10 | int pid; 11 | }; 12 | 13 | typedef struct worker s_worker; 14 | 15 | int run_master() { 16 | long nworkers = read_config_long("maxNumWorkers", "1"); 17 | s_worker workers[nworkers]; 18 | 19 | long numCPU = sysconf(_SC_NPROCESSORS_ONLN); //Only run on max on number of user processors 20 | if(nworkers > numCPU) nworkers = numCPU; 21 | 22 | //Create workers 23 | for(long i=0; i 0) 26 | message_log("Created new worker", DEBUG); 27 | else { 28 | message_log("There was an error during worker creation, aborting...", ERR); 29 | break; 30 | } 31 | } 32 | 33 | //Shutdown master on any interrupt signal 34 | sigset_t sigset; 35 | sigemptyset(&sigset); 36 | sigaddset(&sigset, SIGTERM); 37 | sigaddset(&sigset, SIGINT); 38 | sigaddset(&sigset, SIGKILL); 39 | 40 | /** 41 | * TODO: make any specific master loop here 42 | */ 43 | 44 | //Wait for shutdown signal 45 | int result; 46 | sigwait(&sigset, &result); 47 | 48 | /** 49 | * We should shutdown workers now 50 | */ 51 | for(long i=0; i 8 | Copyright (c) 2012-2014 Daniel J. Bernstein 9 | To the extent possible under law, the author(s) have dedicated all copyright 10 | and related and neighboring rights to this software to the public domain 11 | worldwide. This software is distributed without any warranty. 12 | You should have received a copy of the CC0 Public Domain Dedication along 13 | with 14 | this software. If not, see 15 | . 16 | */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | /* default: SipHash-2-4 */ 23 | #define cROUNDS 2 24 | #define dROUNDS 4 25 | 26 | #define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) 27 | 28 | #define U32TO8_LE(p, v) \ 29 | (p)[0] = (uint8_t)((v)); \ 30 | (p)[1] = (uint8_t)((v) >> 8); \ 31 | (p)[2] = (uint8_t)((v) >> 16); \ 32 | (p)[3] = (uint8_t)((v) >> 24); 33 | 34 | #define U64TO8_LE(p, v) \ 35 | U32TO8_LE((p), (uint32_t)((v))); \ 36 | U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); 37 | 38 | #define U8TO64_LE(p) \ 39 | (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \ 40 | ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \ 41 | ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \ 42 | ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) 43 | 44 | #define SIPROUND \ 45 | do { \ 46 | v0 += v1; \ 47 | v1 = ROTL(v1, 13); \ 48 | v1 ^= v0; \ 49 | v0 = ROTL(v0, 32); \ 50 | v2 += v3; \ 51 | v3 = ROTL(v3, 16); \ 52 | v3 ^= v2; \ 53 | v0 += v3; \ 54 | v3 = ROTL(v3, 21); \ 55 | v3 ^= v0; \ 56 | v2 += v1; \ 57 | v1 = ROTL(v1, 17); \ 58 | v1 ^= v2; \ 59 | v2 = ROTL(v2, 32); \ 60 | } while (0) 61 | 62 | #ifdef DEBUG 63 | #define TRACE \ 64 | do { \ 65 | printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \ 66 | (uint32_t)v0); \ 67 | printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \ 68 | (uint32_t)v1); \ 69 | printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \ 70 | (uint32_t)v2); \ 71 | printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \ 72 | (uint32_t)v3); \ 73 | } while (0) 74 | #else 75 | #define TRACE 76 | #endif 77 | 78 | int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k, 79 | uint8_t *out, const size_t outlen) { 80 | 81 | assert((outlen == 8) || (outlen == 16)); 82 | uint64_t v0 = 0x736f6d6570736575ULL; 83 | uint64_t v1 = 0x646f72616e646f6dULL; 84 | uint64_t v2 = 0x6c7967656e657261ULL; 85 | uint64_t v3 = 0x7465646279746573ULL; 86 | uint64_t k0 = U8TO64_LE(k); 87 | uint64_t k1 = U8TO64_LE(k + 8); 88 | uint64_t m; 89 | int i; 90 | const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); 91 | const int left = inlen & 7; 92 | uint64_t b = ((uint64_t)inlen) << 56; 93 | v3 ^= k1; 94 | v2 ^= k0; 95 | v1 ^= k1; 96 | v0 ^= k0; 97 | 98 | if (outlen == 16) 99 | v1 ^= 0xee; 100 | 101 | for (; in != end; in += 8) { 102 | m = U8TO64_LE(in); 103 | v3 ^= m; 104 | 105 | TRACE; 106 | for (i = 0; i < cROUNDS; ++i) 107 | SIPROUND; 108 | 109 | v0 ^= m; 110 | } 111 | 112 | switch (left) { 113 | case 7: 114 | b |= ((uint64_t)in[6]) << 48; 115 | case 6: 116 | b |= ((uint64_t)in[5]) << 40; 117 | case 5: 118 | b |= ((uint64_t)in[4]) << 32; 119 | case 4: 120 | b |= ((uint64_t)in[3]) << 24; 121 | case 3: 122 | b |= ((uint64_t)in[2]) << 16; 123 | case 2: 124 | b |= ((uint64_t)in[1]) << 8; 125 | case 1: 126 | b |= ((uint64_t)in[0]); 127 | break; 128 | case 0: 129 | break; 130 | } 131 | 132 | v3 ^= b; 133 | 134 | TRACE; 135 | for (i = 0; i < cROUNDS; ++i) 136 | SIPROUND; 137 | 138 | v0 ^= b; 139 | 140 | if (outlen == 16) 141 | v2 ^= 0xee; 142 | else 143 | v2 ^= 0xff; 144 | 145 | TRACE; 146 | for (i = 0; i < dROUNDS; ++i) 147 | SIPROUND; 148 | 149 | b = v0 ^ v1 ^ v2 ^ v3; 150 | U64TO8_LE(out, b); 151 | 152 | if (outlen == 8) 153 | return 0; 154 | 155 | v1 ^= 0xdd; 156 | 157 | TRACE; 158 | for (i = 0; i < dROUNDS; ++i) 159 | SIPROUND; 160 | 161 | b = v0 ^ v1 ^ v2 ^ v3; 162 | U64TO8_LE(out + 8, b); 163 | 164 | return 0; 165 | } 166 | 167 | #endif -------------------------------------------------------------------------------- /src/types.c: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | #include "stdlib.h" 3 | #include "logger.h" 4 | #include "config.h" 5 | 6 | #include 7 | #include 8 | 9 | s_string create_string(char *buf, unsigned long len) { 10 | s_string s; 11 | s.length = len; 12 | s.position = malloc(s.length); 13 | memcpy(s.position, buf, len); 14 | 15 | return s; 16 | } 17 | 18 | void delete_string(s_string *s) { 19 | if(s->position == NULL) 20 | message_log("Tried to free null string!", WARN); 21 | else 22 | free(s->position); 23 | 24 | s->length = 0; 25 | } 26 | 27 | s_string concat_string(s_string s1, s_string s2) { 28 | s_string s3; 29 | 30 | s3.length = s1.length + s2.length; 31 | s3.position = malloc(s3.length); 32 | memcpy(s3.position, s1.position, s1.length); 33 | memcpy(s3.position+s1.length, s2.position, s2.length); 34 | 35 | return s3; 36 | } 37 | 38 | s_string concat_string_const(s_string str, const char *con) { 39 | s_string s3; 40 | 41 | unsigned long len = strlen(con); 42 | s3.length = str.length + len; 43 | s3.position = malloc(s3.length); 44 | memcpy(s3.position, str.position, str.length); 45 | memcpy(s3.position+str.length, con, len); 46 | 47 | return s3; 48 | } 49 | 50 | s_string substring(s_string *haystack, const char *needle) { //find index of first occurence, NULL if not found 51 | s_string sub; 52 | sub.length = 0; 53 | sub.position = NULL; 54 | 55 | long needle_len = strlen(needle); 56 | if(haystack->lengthlength-needle_len; i++) { 59 | 60 | for(unsigned long j=0; jposition; 64 | return sub; 65 | } 66 | if(haystack->position[i+j] == needle[j]) continue; 67 | else break; 68 | } 69 | } 70 | 71 | sub.length = haystack->length; 72 | return sub; 73 | } 74 | 75 | long compare_string(s_string *str1, s_string *str2) { //return 1 if equal, 0 otherwise 76 | if(str1->length != str2->length) return 0; 77 | for(long i = 0; ilength; i++) { 78 | if(str1->position[i] == str2->position[i]) continue; 79 | return 0; 80 | } 81 | return 1; 82 | } 83 | 84 | long compare_string_const(s_string *str, const char con[]) { 85 | if(strlen(con) != str->length) return 0; 86 | for(long i = 0; ilength; i++) { 87 | if(str->position[i] == con[i]) continue; 88 | return 0; 89 | } 90 | return 1; 91 | } 92 | 93 | char *to_c_string(s_string *str) { //remember to free afterwards! 94 | char *new = malloc(str->length + 1); 95 | memcpy(new, str->position, str->length); 96 | new[str->length] = '\0'; 97 | 98 | return new; 99 | } 100 | 101 | void clear_string_list(s_string_list *first) { 102 | 103 | for(s_string_list *current = first; current!=NULL; ) { 104 | s_string_list *prev = current; 105 | current = prev->next; 106 | delete_string(&prev->key); 107 | delete_string(&prev->value); 108 | free(prev); 109 | } 110 | 111 | } 112 | 113 | s_buffer initialize_buffer() { 114 | s_buffer buffer; 115 | buffer.size = 0; 116 | buffer.offset = 0; 117 | buffer.payload = NULL; 118 | return buffer; 119 | } 120 | 121 | int expand_buffer(s_buffer *buffer, long howMuch) { 122 | buffer->size += howMuch; 123 | if(howMuch > 0) { //expand 124 | if (buffer->size == howMuch) //Was inexistent 125 | buffer->payload = malloc((size_t) howMuch); 126 | else { 127 | buffer->payload = realloc(buffer->payload, (size_t) buffer->size); 128 | } 129 | } 130 | else { //shrink 131 | if(buffer->size <= 0) { //free the empty buffer 132 | free(buffer->payload); 133 | buffer->size = 0; 134 | buffer->offset = 0; 135 | } 136 | else { 137 | buffer->payload = realloc(buffer->payload, buffer->size); 138 | } 139 | } 140 | return 0; 141 | } 142 | 143 | void clean_buffer(s_buffer *buffer) { 144 | if(buffer->size > 0) free(buffer->payload); 145 | else { 146 | message_log("Tried to free empty buffer!", WARN); 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #ifndef PUTHTTPD_TYPES_H 2 | #define PUTHTTPD_TYPES_H 3 | 4 | typedef struct { 5 | unsigned long length; 6 | char *position; 7 | } s_string; 8 | 9 | typedef struct s_string_list s_string_list; 10 | 11 | struct s_string_list { 12 | s_string key; 13 | s_string value; 14 | s_string_list *next; 15 | }; 16 | 17 | s_string create_string(char *buf, unsigned long len); 18 | void delete_string(s_string *s); 19 | s_string concat_string(s_string s1, s_string s2); 20 | s_string concat_string_const(s_string str, const char *con); 21 | s_string substring(s_string *haystack, const char *needle); 22 | long compare_string(s_string *str1, s_string *str2); 23 | long compare_string_const(s_string *str, const char *con); 24 | char *to_c_string(s_string *str); 25 | void clear_string_list(s_string_list *first); 26 | 27 | typedef struct { 28 | char *payload; 29 | unsigned long size; 30 | unsigned long offset; 31 | } s_buffer; 32 | 33 | s_buffer initialize_buffer(); 34 | int expand_buffer(s_buffer *buffer, long howMuch); 35 | void clean_buffer(s_buffer *buffer); 36 | 37 | #endif //PUTHTTPD_TYPES_H 38 | -------------------------------------------------------------------------------- /src/worker.c: -------------------------------------------------------------------------------- 1 | #include "worker.h" 2 | #include "logger.h" 3 | #include "config.h" 4 | #include "connection.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | int running; 15 | 16 | void stop_worker() { 17 | running = 0; 18 | } 19 | 20 | int create_worker() { 21 | int pid = fork(); 22 | 23 | if(pid > 0) 24 | return pid; 25 | else if(pid < 0) { 26 | message_log("Unable to create new worker! Exiting...", ERR); 27 | return -1; 28 | } 29 | 30 | /* 31 | * child zone below 32 | */ 33 | 34 | //Connection linked list for timeouts 35 | latest = NULL; 36 | oldest = NULL; 37 | 38 | running = 1; 39 | message_log("I'm working!", DEBUG); 40 | signal(SIGTERM, &stop_worker); 41 | 42 | unsigned short port = (unsigned short)read_config_long("listenPort", "9000"); 43 | 44 | s_tcp_server server; 45 | create_server_struct(&server); 46 | 47 | int epoll_fd = epoll_create1(0); 48 | if(epoll_fd < 0) { 49 | message_log("Failed to create epoll fd. Worker crashed", ERR); 50 | exit(-1); 51 | } 52 | 53 | if(bind_server_socket(port, &server) < 0) { 54 | message_log("Binding socket failed. Worker crashed", ERR); 55 | exit(-1); 56 | } 57 | 58 | struct epoll_event accept_ev; 59 | s_connection *srv_connection = malloc(sizeof(s_connection)); 60 | srv_connection->fd = server.srv_socket; 61 | accept_ev.data.ptr = srv_connection; 62 | accept_ev.events = EPOLLIN | EPOLLET; 63 | if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server.srv_socket, &accept_ev) < 0) { 64 | message_log("Failed to add epoll event", ERR); 65 | running = 0; 66 | } 67 | 68 | struct epoll_event *event_queue; 69 | 70 | long queueSize = read_config_long("queueSize", "64"); 71 | event_queue = calloc((size_t)queueSize, sizeof(accept_ev)); 72 | 73 | while(running) { 74 | int n = epoll_wait(epoll_fd, event_queue, (int)queueSize, -1); 75 | //Connection handling logic 76 | for(int i=0; ifd) { //incoming connections 85 | while(accept_client_connection(&server, epoll_fd) != -1); 86 | continue; 87 | } 88 | else if(event_queue[i].events & EPOLLIN) { //there is incoming data from one of connected clients 89 | //return number of new requests added 90 | long result = read_client_connection(cli_connection); 91 | if(result == 0) { //client disconnected, close connection 92 | message_log("Client disconnected", INFO); 93 | //close connection 94 | close_client_connection(cli_connection); 95 | continue; 96 | } 97 | else if(result == -1) { //invalid request, return 400 98 | message_log("BAD REQUEST!", ERR); 99 | cli_connection->currentRequest->status = BAD_REQUEST; 100 | } 101 | 102 | int proc_result = process_client_connection(cli_connection); //process request read 103 | if(proc_result < 0 && cli_connection->currentRequest != NULL) { 104 | message_log("Error 500", ERR); 105 | cli_connection->currentRequest->status = BAD_REQUEST; 106 | } 107 | } 108 | else if(event_queue[i].events & EPOLLOUT) { //one of connected clients is ready to read 109 | if(cli_connection->response_buffer.size > 0) { //there is some data remaining so let's send it 110 | long result = write_client_connection(cli_connection); 111 | if(result<0) { 112 | message_log("Closing connection to client", INFO); 113 | //close connection 114 | close_client_connection(cli_connection); 115 | continue; 116 | } 117 | } 118 | } 119 | 120 | //Update connection drop timers after request was fullfiled 121 | cli_connection->drop_timeout = time(NULL) + get_global_config()->request_timeout_sec; 122 | if(cli_connection != latest) { 123 | //detach connection from the middle of list 124 | detach_client_connection(cli_connection); 125 | //add connection on top of the linked list 126 | cli_connection->prev = latest; 127 | if (latest != NULL) 128 | latest->next = cli_connection; 129 | latest = cli_connection; 130 | } 131 | } 132 | 133 | //Stale connection dropping logic 134 | while(oldest != NULL && oldest->drop_timeout < time(NULL)) { 135 | message_log("Request timeout", INFO); 136 | if(close_client_connection(oldest) < 0) break; 137 | } 138 | } 139 | 140 | close_server_socket(&server); 141 | 142 | exit(0); 143 | } 144 | 145 | int shutdown_worker(int pid) { 146 | int status; 147 | kill(pid, SIGTERM); 148 | waitpid(pid, &status, 0); 149 | 150 | if(status < 0) { 151 | message_log("The child process terminated with error", ERR); 152 | return -1; 153 | } 154 | 155 | return 0; 156 | } -------------------------------------------------------------------------------- /src/worker.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by mbien on 19.10.18. 3 | // 4 | 5 | #ifndef PUTHTTPD_WORKER_H 6 | #define PUTHTTPD_WORKER_H 7 | 8 | #include "connection.h" 9 | 10 | int create_worker(); 11 | int shutdown_worker(int pid); 12 | 13 | s_connection *latest; 14 | s_connection *oldest; 15 | 16 | #endif //PUTHTTPD_WORKER_H 17 | --------------------------------------------------------------------------------