├── .gitignore ├── 400.html ├── 404.html ├── README.md ├── makefile ├── public_html └── index.html └── server.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the compiled file 2 | server 3 | 4 | # Ignore any text editor backups 5 | *~ 6 | 7 | 8 | -------------------------------------------------------------------------------- /400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 400 - Bad Request 6 | 7 | 8 | 9 | 10 | 11 | 400 Bad Request 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 - Page Not Found 6 | 7 | 8 | 9 | 10 | 11 | Woops couldn't find that page 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Simple Webserver Written in C 2 | =============================== 3 | 4 | This is a simple webserver written in the C programming language. 5 | 6 | It uses a pool of 10 connections to serve multiple requests concurrently and it keeps track of how much data it has output, printing it to the standard output stream. 7 | 8 | 9 | Compiling and Using the System 10 | ============================== 11 | 12 | On a Linux system system simply use the makefile to compile the server. 13 | 14 | On a Mac use this command to compile the server: 15 | 16 | gcc cs241server.c –o cs241server 17 | 18 | 19 | To run the server type ./server into a terminal that is in the directory where the executable file is located. 20 | 21 | By default the server runs on port 2001, so to try it out navigate to 22 | 23 | localhost:2001 in a webbrowser 24 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | server : server.c 2 | gcc server.c -lrt -o server 3 | -------------------------------------------------------------------------------- /public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Well Done ! 6 | 7 | 8 | 9 | 10 | 11 | It looks like everything is working ! 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | #include // socket definitions 2 | #include // socket types 3 | #include // inet (3) funtions 4 | #include // misc. UNIX functions 5 | #include // signal handling 6 | #include // standard library 7 | #include // input/output library 8 | #include // string library 9 | #include // error number library 10 | #include // for O_* constants 11 | #include // mmap library 12 | #include // various type definitions 13 | #include // more constants 14 | 15 | // global constants 16 | #define PORT 2001 // port to connect on 17 | #define LISTENQ 10 // number of connections 18 | 19 | int list_s; // listening socket 20 | 21 | // structure to hold the return code and the filepath to serve to client. 22 | typedef struct { 23 | int returncode; 24 | char *filename; 25 | } httpRequest; 26 | 27 | // Structure to hold variables that will be placed in shared memory 28 | typedef struct { 29 | pthread_mutex_t mutexlock; 30 | int totalbytes; 31 | } sharedVariables; 32 | 33 | // headers to send to clients 34 | char *header200 = "HTTP/1.0 200 OK\nServer: CS241Serv v0.1\nContent-Type: text/html\n\n"; 35 | char *header400 = "HTTP/1.0 400 Bad Request\nServer: CS241Serv v0.1\nContent-Type: text/html\n\n"; 36 | char *header404 = "HTTP/1.0 404 Not Found\nServer: CS241Serv v0.1\nContent-Type: text/html\n\n"; 37 | 38 | // get a message from the socket until a blank line is recieved 39 | char *getMessage(int fd) { 40 | 41 | // A file stream 42 | FILE *sstream; 43 | 44 | // Try to open the socket to the file stream and handle any failures 45 | if( (sstream = fdopen(fd, "r")) == NULL) 46 | { 47 | fprintf(stderr, "Error opening file descriptor in getMessage()\n"); 48 | exit(EXIT_FAILURE); 49 | } 50 | 51 | // Size variable for passing to getline 52 | size_t size = 1; 53 | 54 | char *block; 55 | 56 | // Allocate some memory for block and check it went ok 57 | if( (block = malloc(sizeof(char) * size)) == NULL ) 58 | { 59 | fprintf(stderr, "Error allocating memory to block in getMessage\n"); 60 | exit(EXIT_FAILURE); 61 | } 62 | 63 | // Set block to null 64 | *block = '\0'; 65 | 66 | // Allocate some memory for tmp and check it went ok 67 | char *tmp; 68 | if( (tmp = malloc(sizeof(char) * size)) == NULL ) 69 | { 70 | fprintf(stderr, "Error allocating memory to tmp in getMessage\n"); 71 | exit(EXIT_FAILURE); 72 | } 73 | // Set tmp to null 74 | *tmp = '\0'; 75 | 76 | // Int to keep track of what getline returns 77 | int end; 78 | // Int to help use resize block 79 | int oldsize = 1; 80 | 81 | // While getline is still getting data 82 | while( (end = getline( &tmp, &size, sstream)) > 0) 83 | { 84 | // If the line its read is a caridge return and a new line were at the end of the header so break 85 | if( strcmp(tmp, "\r\n") == 0) 86 | { 87 | break; 88 | } 89 | 90 | // Resize block 91 | block = realloc(block, size+oldsize); 92 | // Set the value of oldsize to the current size of block 93 | oldsize += size; 94 | // Append the latest line we got to block 95 | strcat(block, tmp); 96 | } 97 | 98 | // Free tmp a we no longer need it 99 | free(tmp); 100 | 101 | // Return the header 102 | return block; 103 | 104 | } 105 | 106 | // send a message to a socket file descripter 107 | int sendMessage(int fd, char *msg) { 108 | return write(fd, msg, strlen(msg)); 109 | } 110 | 111 | // Extracts the filename needed from a GET request and adds public_html to the front of it 112 | char * getFileName(char* msg) 113 | { 114 | // Variable to store the filename in 115 | char * file; 116 | // Allocate some memory for the filename and check it went OK 117 | if( (file = malloc(sizeof(char) * strlen(msg))) == NULL) 118 | { 119 | fprintf(stderr, "Error allocating memory to file in getFileName()\n"); 120 | exit(EXIT_FAILURE); 121 | } 122 | 123 | // Get the filename from the header 124 | sscanf(msg, "GET %s HTTP/1.1", file); 125 | 126 | // Allocate some memory not in read only space to store "public_html" 127 | char *base; 128 | if( (base = malloc(sizeof(char) * (strlen(file) + 18))) == NULL) 129 | { 130 | fprintf(stderr, "Error allocating memory to base in getFileName()\n"); 131 | exit(EXIT_FAILURE); 132 | } 133 | 134 | char* ph = "public_html"; 135 | 136 | // Copy public_html to the non read only memory 137 | strcpy(base, ph); 138 | 139 | // Append the filename after public_html 140 | strcat(base, file); 141 | 142 | // Free file as we now have the file name in base 143 | free(file); 144 | 145 | // Return public_html/filetheywant.html 146 | return base; 147 | } 148 | 149 | // parse a HTTP request and return an object with return code and filename 150 | httpRequest parseRequest(char *msg){ 151 | httpRequest ret; 152 | 153 | // A variable to store the name of the file they want 154 | char* filename; 155 | // Allocate some memory to filename and check it goes OK 156 | if( (filename = malloc(sizeof(char) * strlen(msg))) == NULL) 157 | { 158 | fprintf(stderr, "Error allocating memory to filename in parseRequest()\n"); 159 | exit(EXIT_FAILURE); 160 | } 161 | // Find out what page they want 162 | filename = getFileName(msg); 163 | 164 | // Check if its a directory traversal attack 165 | char *badstring = ".."; 166 | char *test = strstr(filename, badstring); 167 | 168 | // Check if they asked for / and give them index.html 169 | int test2 = strcmp(filename, "public_html/"); 170 | 171 | // Check if the page they want exists 172 | FILE *exists = fopen(filename, "r" ); 173 | 174 | // If the badstring is found in the filename 175 | if( test != NULL ) 176 | { 177 | // Return a 400 header and 400.html 178 | ret.returncode = 400; 179 | ret.filename = "400.html"; 180 | } 181 | 182 | // If they asked for / return index.html 183 | else if(test2 == 0) 184 | { 185 | ret.returncode = 200; 186 | ret.filename = "public_html/index.html"; 187 | } 188 | 189 | // If they asked for a specific page and it exists because we opened it sucessfully return it 190 | else if( exists != NULL ) 191 | { 192 | 193 | ret.returncode = 200; 194 | ret.filename = filename; 195 | // Close the file stream 196 | fclose(exists); 197 | } 198 | 199 | // If we get here the file they want doesn't exist so return a 404 200 | else 201 | { 202 | ret.returncode = 404; 203 | ret.filename = "404.html"; 204 | } 205 | 206 | // Return the structure containing the details 207 | return ret; 208 | } 209 | 210 | // print a file out to a socket file descriptor 211 | int printFile(int fd, char *filename) { 212 | 213 | /* Open the file filename and echo the contents from it to the file descriptor fd */ 214 | 215 | // Attempt to open the file 216 | FILE *read; 217 | if( (read = fopen(filename, "r")) == NULL) 218 | { 219 | fprintf(stderr, "Error opening file in printFile()\n"); 220 | exit(EXIT_FAILURE); 221 | } 222 | 223 | // Get the size of this file for printing out later on 224 | int totalsize; 225 | struct stat st; 226 | stat(filename, &st); 227 | totalsize = st.st_size; 228 | 229 | // Variable for getline to write the size of the line its currently printing to 230 | size_t size = 1; 231 | 232 | // Get some space to store each line of the file in temporarily 233 | char *temp; 234 | if( (temp = malloc(sizeof(char) * size)) == NULL ) 235 | { 236 | fprintf(stderr, "Error allocating memory to temp in printFile()\n"); 237 | exit(EXIT_FAILURE); 238 | } 239 | 240 | 241 | // Int to keep track of what getline returns 242 | int end; 243 | 244 | // While getline is still getting data 245 | while( (end = getline( &temp, &size, read)) > 0) 246 | { 247 | sendMessage(fd, temp); 248 | } 249 | 250 | // Final new line 251 | sendMessage(fd, "\n"); 252 | 253 | // Free temp as we no longer need it 254 | free(temp); 255 | 256 | // Return how big the file we sent out was 257 | return totalsize; 258 | 259 | } 260 | 261 | // clean up listening socket on ctrl-c 262 | void cleanup(int sig) { 263 | 264 | printf("Cleaning up connections and exiting.\n"); 265 | 266 | // try to close the listening socket 267 | if (close(list_s) < 0) { 268 | fprintf(stderr, "Error calling close()\n"); 269 | exit(EXIT_FAILURE); 270 | } 271 | 272 | // Close the shared memory we used 273 | shm_unlink("/sharedmem"); 274 | 275 | // exit with success 276 | exit(EXIT_SUCCESS); 277 | } 278 | 279 | int printHeader(int fd, int returncode) 280 | { 281 | // Print the header based on the return code 282 | switch (returncode) 283 | { 284 | case 200: 285 | sendMessage(fd, header200); 286 | return strlen(header200); 287 | break; 288 | 289 | case 400: 290 | sendMessage(fd, header400); 291 | return strlen(header400); 292 | break; 293 | 294 | case 404: 295 | sendMessage(fd, header404); 296 | return strlen(header404); 297 | break; 298 | } 299 | } 300 | 301 | 302 | // Increment the global count of data sent out 303 | int recordTotalBytes(int bytes_sent, sharedVariables *mempointer) 304 | { 305 | // Lock the mutex 306 | pthread_mutex_lock(&(*mempointer).mutexlock); 307 | // Increment bytes_sent 308 | (*mempointer).totalbytes += bytes_sent; 309 | // Unlock the mutex 310 | pthread_mutex_unlock(&(*mempointer).mutexlock); 311 | // Return the new byte count 312 | return (*mempointer).totalbytes; 313 | } 314 | 315 | 316 | int main(int argc, char *argv[]) { 317 | int conn_s; // connection socket 318 | short int port = PORT; // port number 319 | struct sockaddr_in servaddr; // socket address structure 320 | 321 | // set up signal handler for ctrl-c 322 | (void) signal(SIGINT, cleanup); 323 | 324 | // create the listening socket 325 | if ((list_s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { 326 | fprintf(stderr, "Error creating listening socket.\n"); 327 | exit(EXIT_FAILURE); 328 | } 329 | 330 | // set all bytes in socket address structure to zero, and fill in the relevant data members 331 | memset(&servaddr, 0, sizeof(servaddr)); 332 | servaddr.sin_family = AF_INET; 333 | servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 334 | servaddr.sin_port = htons(port); 335 | 336 | // bind to the socket address 337 | if (bind(list_s, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 ) { 338 | fprintf(stderr, "Error calling bind()\n"); 339 | exit(EXIT_FAILURE); 340 | } 341 | 342 | 343 | // Listen on socket list_s 344 | if( (listen(list_s, 10)) == -1) 345 | { 346 | fprintf(stderr, "Error Listening\n"); 347 | exit(EXIT_FAILURE); 348 | } 349 | 350 | // Set up some shared memory to store our shared variables in 351 | 352 | // Close the shared memory we use just to be safe 353 | shm_unlink("/sharedmem"); 354 | 355 | int sharedmem; 356 | 357 | // Open the memory 358 | if( (sharedmem = shm_open("/sharedmem", O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1) 359 | { 360 | fprintf(stderr, "Error opening sharedmem in main() errno is: %s ", strerror(errno)); 361 | exit(EXIT_FAILURE); 362 | } 363 | 364 | // Set the size of the shared memory to the size of my structure 365 | ftruncate(sharedmem, sizeof(sharedVariables) ); 366 | 367 | // Map the shared memory into our address space 368 | sharedVariables *mempointer; 369 | 370 | // Set mempointer to point at the shared memory 371 | mempointer = mmap(NULL, sizeof(sharedVariables), PROT_READ | PROT_WRITE, MAP_SHARED, sharedmem, 0); 372 | 373 | // Check the memory allocation went OK 374 | if( mempointer == MAP_FAILED ) 375 | { 376 | fprintf(stderr, "Error setting shared memory for sharedVariables in recordTotalBytes() error is %d \n ", errno); 377 | exit(EXIT_FAILURE); 378 | } 379 | // Initalise the mutex 380 | pthread_mutex_init(&(*mempointer).mutexlock, NULL); 381 | // Set total bytes sent to 0 382 | (*mempointer).totalbytes = 0; 383 | 384 | // Size of the address 385 | int addr_size = sizeof(servaddr); 386 | 387 | // Sizes of data were sending out 388 | int headersize; 389 | int pagesize; 390 | int totaldata; 391 | // Number of child processes we have spawned 392 | int children = 0; 393 | // Variable to store the ID of the process we get when we spawn 394 | pid_t pid; 395 | 396 | // Loop infinitly serving requests 397 | while(1) 398 | { 399 | 400 | // If we haven't already spawned 10 children fork 401 | if( children <= 10) 402 | { 403 | pid = fork(); 404 | children++; 405 | } 406 | 407 | // If the pid is -1 the fork failed so handle that 408 | if( pid == -1) 409 | { 410 | fprintf(stderr,"can't fork, error %d\n" , errno); 411 | exit (1); 412 | } 413 | 414 | // Have the child process deal with the connection 415 | if ( pid == 0) 416 | { 417 | // Have the child loop infinetly dealing with a connection then getting the next one in the queue 418 | while(1) 419 | { 420 | // Accept a connection 421 | conn_s = accept(list_s, (struct sockaddr *)&servaddr, &addr_size); 422 | 423 | // If something went wrong with accepting the connection deal with it 424 | if(conn_s == -1) 425 | { 426 | fprintf(stderr,"Error accepting connection \n"); 427 | exit (1); 428 | } 429 | 430 | // Get the message from the file descriptor 431 | char * header = getMessage(conn_s); 432 | 433 | // Parse the request 434 | httpRequest details = parseRequest(header); 435 | 436 | // Free header now were done with it 437 | free(header); 438 | 439 | // Print out the correct header 440 | headersize = printHeader(conn_s, details.returncode); 441 | 442 | // Print out the file they wanted 443 | pagesize = printFile(conn_s, details.filename); 444 | 445 | // Increment our count of total datasent by all processes and get back the new total 446 | totaldata = recordTotalBytes(headersize+pagesize, mempointer); 447 | 448 | // Print out which process handled the request and how much data was sent 449 | printf("Process %d served a request of %d bytes. Total bytes sent %d \n", getpid(), headersize+pagesize, totaldata); 450 | 451 | // Close the connection now were done 452 | close(conn_s); 453 | } 454 | } 455 | } 456 | 457 | return EXIT_SUCCESS; 458 | } 459 | --------------------------------------------------------------------------------