├── .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 |
--------------------------------------------------------------------------------