├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmark └── node-server.js ├── libs ├── CMakeLists.txt ├── helpers.c ├── helpers.h ├── macros.h └── structs.h └── src └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | vendor/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(mini-http-server) 3 | 4 | set(TARGET_FILE "http-echo-server") 5 | set(CMAKE_BUILD_TYPE Release) 6 | set(CMAKE_C_STANDARD 17) 7 | 8 | # a simple way to check non-standard C header files (includes the atomic-related one). 9 | include(CheckIncludeFiles) 10 | check_include_files("pthread.h;stdatomic.h;sys/socket.h;netinet/in.h;unistd.h" EDEPS) 11 | if (EPTHREAD EQUAL 1) 12 | message(FATAL_ERROR "Necessary header files are not found!") 13 | endif() 14 | 15 | # for headers in "/libs" and other external installed packages. 16 | include_directories(. /usr/local/include) 17 | 18 | # load source files and sub-directories. 19 | aux_source_directory(./src DIR_SRCS) 20 | add_subdirectory(libs/) 21 | 22 | # load packages. 23 | find_package(uriparser 0.9.6 CONFIG REQUIRED char) 24 | 25 | # for executable. 26 | add_executable(${TARGET_FILE} ${DIR_SRCS}) 27 | target_link_libraries(${TARGET_FILE} PUBLIC core m pthread uriparser::uriparser) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jason Yu 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 | # tiny-http-echo-server 2 | A simple HTTP Echo Server, just for educational purposes. 3 | 4 | If you send the HTTP request with a query parameter named "num" and an integer value N, then the server will respond to you with the Nth value in the standard Fibonacci sequence. 5 | 6 | ### Compilation 7 | ``` 8 | mkdir build && cd build && cmake .. && cmake --build . 9 | ``` 10 | 11 | ### Run Server 12 | ``` 13 | ./build/main thread_count=4 14 | ``` 15 | 16 | ### Load Test 17 | ``` 18 | ab -c 50 -n 100 http://127.0.0.1:8080/?num=40 19 | ``` 20 | -------------------------------------------------------------------------------- /benchmark/node-server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const url = require('url') 3 | 4 | const __calcFibTCO = (n, x, y) => { 5 | if (n == 0) 6 | return x 7 | if (n == 1) 8 | return y 9 | return __calcFibTCO(n - 1, y, x + y) 10 | } 11 | 12 | const __calcFibRecursion = (n) => { 13 | if (n <= 1) 14 | return n 15 | return __calcFibRecursion(n - 1) + __calcFibRecursion(n - 2) 16 | } 17 | 18 | const calcFibonacci = (n) => { 19 | return __calcFibRecursion(n) 20 | } 21 | 22 | let app = http.createServer((req, res) => { 23 | const { num } = url.parse(req.url, true).query 24 | res.writeHead(200, { 'Content-Type': 'text/plain' }) 25 | const result = calcFibonacci(num ? +num : '') 26 | res.end(result + ''); 27 | }); 28 | 29 | app.listen(8080, '127.0.0.1') 30 | console.log('Server is now listening at port 8080: ') 31 | -------------------------------------------------------------------------------- /libs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(. DIR_LIB_SRCS) 2 | add_library(core ${DIR_LIB_SRCS}) 3 | -------------------------------------------------------------------------------- /libs/helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "helpers.h" 6 | #include "structs.h" 7 | 8 | int __calcFibTCO(int n, int x, int y) { 9 | if (n == 0) 10 | return x; 11 | if (n == 1) 12 | return y; 13 | return __calcFibTCO(n - 1, y, x + y); 14 | } 15 | 16 | int __calcFibRecursion(int n) { 17 | if (n <= 1) 18 | return n; 19 | return __calcFibRecursion(n - 1) + __calcFibRecursion(n - 2); 20 | } 21 | 22 | int calcFibonacci(int n) { 23 | // return __calcFibTCO(n, 0, 1); // TCO version. 24 | return __calcFibRecursion(n); // recursion version. 25 | } 26 | 27 | int calcDigits(int n) { 28 | return n == 0 ? 0 : (int) floor(log10(abs(n))) + 1; 29 | } 30 | 31 | void wrapStrFromPTR(char* str, size_t len, const char* head, const char* tail) { 32 | for (size_t i = 0; head != tail; head++) 33 | str[i++] = *head; 34 | str[len - 1] = '\0'; 35 | } 36 | 37 | int retrieveGETQueryIntValByKey(char* req, const char* key) { 38 | int result = 0; 39 | 40 | // extract uri; 41 | const char* uriHead = strchr(req, ' ') + 1; 42 | const char* uriTail = strchr(uriHead, ' '); 43 | size_t uriLen = uriTail - uriHead + 1; 44 | char strUri[uriLen]; 45 | wrapStrFromPTR(strUri, uriLen, uriHead, uriTail); 46 | 47 | // parse uri; 48 | UriUriA uri; 49 | UriQueryListA* queryList; 50 | int itemCount; 51 | const char* errorPos; 52 | if (uriParseSingleUriA(&uri, strUri, &errorPos) == URI_SUCCESS) { 53 | if (uriDissectQueryMallocA(&queryList, &itemCount, uri.query.first, uri.query.afterLast) == URI_SUCCESS) { 54 | while (itemCount--) { 55 | if (strcmp(queryList->key, key) == 0) { 56 | result = atoi(queryList->value); 57 | break; 58 | } 59 | queryList = queryList->next; 60 | } 61 | uriFreeQueryListA(queryList); 62 | } 63 | } 64 | return result; 65 | } 66 | 67 | void setupServerSettings(int argc, const char** argv, serverSettings* ss) { 68 | while (argc-- > 1) { 69 | // process key. 70 | const char* keyHead = argv[argc]; 71 | const char* keyPos = strchr(keyHead, '='); 72 | const size_t keyLen = keyPos - keyHead + 1; 73 | char key[keyLen]; 74 | wrapStrFromPTR(key, keyLen, keyHead, keyPos); 75 | // process value. 76 | const char* valHead = keyHead + keyLen; 77 | const char* valPos = strchr(valHead, '\0'); 78 | const size_t valLen = valPos - valHead + 1; 79 | char val[valLen]; 80 | for (size_t i = 0; valHead <= valPos; valHead++) 81 | val[i++] = *valHead; 82 | if (strcmp(key, "thread_count") == 0) { 83 | ss->thread_count = atoi(val); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /libs/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIBS_HELPERS 2 | #define _LIBS_HELPERS 3 | 4 | #include "structs.h" 5 | 6 | int calcFibonacci(int); 7 | int __calcFibTCO(int, int, int); 8 | int __calcFibRecursion(int); 9 | int calcDigits(int); 10 | int retrieveGETQueryIntValByKey(char*, const char*); 11 | void wrapStrFromPTR(char*, size_t, const char*, const char*); 12 | void setupServerSettings(int, const char**, serverSettings*); 13 | 14 | #endif // _LIBS_HELPERS 15 | -------------------------------------------------------------------------------- /libs/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIBS_MACROS 2 | #define _LIBS_MACROS 3 | 4 | // macro constants. 5 | #define PORT 8080 6 | #define MAX_LISTEN_CONN 128 7 | #define HTTP_REQ_BUF 1024 8 | #define HTTP_RES_BUF 1024 9 | 10 | #endif // _LIBS_MACROS 11 | -------------------------------------------------------------------------------- /libs/structs.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIBS_STRUCTS 2 | #define _LIBS_STRUCTS 3 | 4 | #include 5 | 6 | // self-defined types. 7 | typedef struct sockaddr_in sockaddr_in; 8 | typedef struct sockaddr sockaddr; 9 | typedef struct { 10 | int thread_count; 11 | } serverSettings; 12 | typedef struct { 13 | int serverFd; 14 | sockaddr* addr; 15 | socklen_t* addrLen; 16 | } acceptParams; 17 | 18 | #endif // _LIBS_STRUCTS 19 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "libs/helpers.h" 12 | #include "libs/structs.h" 13 | #include "libs/macros.h" 14 | 15 | // global variables. 16 | atomic_int threadCounter = 0; 17 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 18 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 19 | 20 | void renewThread(void *arg) { 21 | int* acceptedSocket = (int*) arg; 22 | close(*acceptedSocket); 23 | pthread_mutex_lock(&mutex); 24 | threadCounter--; 25 | pthread_cond_signal(&cond); // notify main thread. 26 | pthread_mutex_unlock(&mutex); 27 | } 28 | 29 | noreturn void* acceptConn(void *arg) { 30 | acceptParams* ap = (acceptParams*) arg; 31 | int acceptedSocket; 32 | 33 | while (1) { 34 | pthread_cleanup_push(renewThread, &acceptedSocket); 35 | // extracts a request from the queue. 36 | if ((acceptedSocket = accept(ap->serverFd, ap->addr, ap->addrLen)) < 0) { 37 | perror("In accept"); 38 | pthread_exit(NULL); 39 | } 40 | 41 | // deal with HTTP request. 42 | char reqBuf[HTTP_REQ_BUF]; 43 | bzero(reqBuf, HTTP_REQ_BUF); 44 | const size_t receivedBytes = read(acceptedSocket, reqBuf, HTTP_REQ_BUF); 45 | if (receivedBytes > 0) { 46 | char resBuf[HTTP_RES_BUF]; 47 | 48 | // retrieve number from query. 49 | pthread_mutex_lock(&mutex); 50 | const int num = retrieveGETQueryIntValByKey(reqBuf, "num"); 51 | pthread_mutex_unlock(&mutex); 52 | 53 | int fibResult = calcFibonacci(num); 54 | // follow the format of the http response. 55 | sprintf(resBuf, "HTTP/1.1 200 OK\r\n" 56 | "Content-type: text/plain\r\n" 57 | "Content-length: %d\r\n\r\n%d", calcDigits(fibResult), fibResult); 58 | write(acceptedSocket, resBuf, strlen(resBuf)); 59 | } 60 | close(acceptedSocket); 61 | pthread_cleanup_pop(0); 62 | } 63 | } 64 | 65 | int main(int argc, const char* argv[]) { 66 | // initialize the server setup. 67 | serverSettings ss = { .thread_count = 4 }; 68 | setupServerSettings(argc, argv, &ss); 69 | 70 | int serverFd; 71 | sockaddr_in address; 72 | int addrLen = sizeof(address); 73 | 74 | // establish a socket. 75 | if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { 76 | perror("In socket creation"); 77 | exit(EXIT_FAILURE); 78 | } 79 | 80 | bzero(&address, addrLen); 81 | address.sin_family = AF_INET; 82 | address.sin_addr.s_addr = INADDR_ANY; // -> 0.0.0.0. 83 | address.sin_port = htons(PORT); 84 | 85 | // assigns specified address to the socket. 86 | if (bind(serverFd, (sockaddr*) &address, sizeof(address)) < 0) { 87 | perror("In bind"); 88 | exit(EXIT_FAILURE); 89 | } 90 | 91 | // mark the socket as a passive socket. 92 | if (listen(serverFd, MAX_LISTEN_CONN) < 0) { 93 | perror("In listen"); 94 | exit(EXIT_FAILURE); 95 | } 96 | printf("\nServer is now listening at port %d:\n\n", PORT); 97 | 98 | // main loop. 99 | while (1) { 100 | pthread_mutex_lock(&mutex); 101 | while (threadCounter >= ss.thread_count) 102 | pthread_cond_wait(&cond, &mutex); 103 | pthread_mutex_unlock(&mutex); 104 | 105 | // create new thread to handle the request. 106 | pthread_t thread_id; 107 | acceptParams ap = { serverFd, (sockaddr*) &address, (socklen_t*) &addrLen }; 108 | pthread_create(&thread_id, NULL, acceptConn, &ap); 109 | atomic_fetch_add(&threadCounter, 1); 110 | printf("[Info] Thread Created: No.%d\n", threadCounter); 111 | } 112 | return EXIT_SUCCESS; 113 | } 114 | --------------------------------------------------------------------------------