I recently made this simple server in C for Linux systems and was wanting to get another set of eyes on it for a review of the design. I am new to socket programming and used a textbook from school to guide my way.
It is for GET requests from browsers and serves from a public folder (or other destination of choice). It seems to work with all the files I have thrown at it so far. If you test it out, keep in mind that it only works with files that have their type extensions. For example, "index.html" would have to be requested as "index.html" in the browser. I was wanting information about how sessions and POST requests also work on professional servers
Net.h
#ifndef NET_H_INCLUDED
#define NET_H_INCLUDED
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/**
* common functions for TCP IP4 server
*
********************************************************************/
typedef struct sockaddr_in SA_in;
typedef struct sockaddr SA;
// configure server address
int configure_server_IP4(struct sockaddr_in *, uint16_t, int);
int sanitary(const char *);
int compareFileType(const char *, const char *);
int sendPage(const char *, int);
typedef struct {
int fd_client;
char *file_name;
} GetRequest;
GetRequest * prepareGetRequest(int, const char *, const char *);
void * handleGetRequest(void *);
#endif // NET_H_INCLUDED
Net.c
#include "Net.h"
const char *HEADER = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n";
const char *FILE_NOT_FOUND = "<!DOCTYPE html>\r\n"
"<html>\r\n"
"<head>\r\n"
" <title>404 Error</title>\r\n"
"</head>\r\n"
"<body>\r\n"
" <h1>404 Error: File Not Found</h1>\r\n"
"</body>\r\n"
"</html>\r\n";
/**
* common functions for TCP IP4 server
*
********************************************************************/
// configure server address
int configure_server_IP4(SA_in *addr_server_p,
uint16_t PORT, int back_log) {
int fd_server, status;
// clear memory for address
memset(addr_server_p, 0, sizeof(*addr_server_p));
// set up server address
addr_server_p->sin_family = AF_INET; // internet
addr_server_p->sin_port = htons(PORT); // host to network short
addr_server_p->sin_addr.s_addr = INADDR_ANY; // 0
// get socket from OS
if((fd_server = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("server socket failure\n");
exit(1);
} else
printf("server socket: %i\n", fd_server);
// bind server socket to server address
status = bind(
fd_server,
(SA *) addr_server_p,
sizeof(SA)
);
if(status < 0) {
perror("socket binding failure\n");
exit(1);
} else
printf("socket binding success\n");
// listen for connection from clients
status = listen(fd_server, back_log);
if(status < 0) {
perror("failure to start listening\n");
exit(1);
} else
printf("listening on port: %i\n", PORT);
// return file descriptor for client
return fd_server;
}
int sanitary(const char *file_name) {
size_t end = strlen(file_name) - 1;
for(int i = 0; i < end; ++i)
if(file_name[i] == '.' && file_name[i + 1] == '.')
return 0;
return 1;
}
int compareFileType(const char *file_name, const char *file_type) {
int l_name, l_type, i, j;
l_name = strlen(file_name);
l_type = strlen(file_type);
printf("file name: %s\n", file_name);
if(l_type > l_name)
return 0;
for(i = l_name - 1, j = l_type - 1; j >= 0; --i, --j)
if(file_name[i] != file_type[j])
return 0;
return 1;
}
int sendPage(const char *file_name, int fd_client) {
int fd_storage;
size_t file_size;
struct stat st;
// account for unsanitary file names and files not found
if(!sanitary(file_name) || stat(file_name, &st) < 0) {
// write HEADER to client
write(fd_client, HEADER, strlen(HEADER));
// write FILE_NOT_FOUND to client
write(fd_client, FILE_NOT_FOUND, strlen(FILE_NOT_FOUND));
return 0;
}
// open file
if((fd_storage = open(file_name, O_RDONLY)) < 0) {
perror("unable to open file\n");
return 0;
}
printf("file opened\n");
// account for html file header
if(compareFileType(file_name, "html"))
write(fd_client, HEADER, strlen(HEADER));
// send file to client
sendfile(fd_client, fd_storage, NULL, st.st_size + 1);
// close file
close(fd_storage);
printf("file closed\n");
return 1;
}
GetRequest * prepareGetRequest(int fd_client,
const char *location, const char *buffer) {
char c,
*file_name;
size_t left,
right,
length_location,
length_file_name,
length_combined;
GetRequest *getR;
length_location = strlen(location);
for(left = 0; buffer[left] != '\0'; ++left)
if(buffer[left] == '/')
break;
for(right = left; buffer[right] != '\0'; ++right) {
c = buffer[right];
if(!isalnum(c) && c != '.' && c != '-' && c != '/')
break;
}
length_file_name = right - left;
length_combined = length_location + length_file_name;
file_name = malloc((length_combined + 1) * sizeof(char));
if(file_name == NULL) {
perror("memory allocation error\n");
return NULL;
}
// put location in front of file name
strncpy(file_name, location, length_location);
// put buffer contents in file name
for(size_t i = length_location; i < length_combined && left < right; ++i, ++left)
file_name[i] = buffer[left];
file_name[length_combined] = '\0';
if((getR = malloc(sizeof(GetRequest))) == NULL) {
free(file_name);
return NULL;
}
getR->fd_client = fd_client;
getR->file_name = file_name;
return getR;
}
void * handleGetRequest(void *generic) {
GetRequest *getR;
int fd_client;
char *file_name;
getR = (GetRequest *) generic;
fd_client = getR->fd_client;
file_name = getR->file_name;
sendPage(file_name, fd_client);
printf("closing fd_client: %i\n\n", fd_client);
close(fd_client);
// free memory for file name
free(getR), free(file_name);
return NULL;
}
server.c
#include "Net.h"
#include <pthread.h>
int main() {
uint16_t PORT = 3000;
int back_log = 20;
// request buffer
const size_t SIZE = 1024;
char request_buffer[SIZE + 1];
char *location = "public";
// socket file descriptors
int fd_server,
fd_client;
GetRequest *getR;
// ipv4 socket addresses
SA_in addr_server,
addr_client;
socklen_t socket_length = sizeof(SA_in);
// pthread id
pthread_t thread_id;
// configure server and begin waiting for connections
fd_server = configure_server_IP4(&addr_server, PORT, back_log);
/**
* acceptance loop for clients
*
***************************************************************************/
while(1) {
// clear memory for address
memset(&addr_client, 0, sizeof(addr_client));
if((fd_client = accept(fd_server, (SA *) &addr_client, &socket_length)) < 0) {
perror("client not accepted\n");
continue;
}
printf("client accepted...\n\n");
// recieve request from client
memset(request_buffer, '\0', (SIZE + 1) * sizeof(char));
read(fd_client, request_buffer, SIZE);
printf("%s\n", request_buffer);
// prepare GET request
if((getR = prepareGetRequest(fd_client, location, request_buffer)) == NULL)
continue;
// single thread
// handleGetRequest(getR);
// if possible, handle GET request in seperate thread
if(pthread_create(&thread_id, NULL, handleGetRequest, getR) != 0)
handleGetRequest(getR);
else if(pthread_detach(thread_id) != 0)
pthread_join(thread_id, NULL);
}
exit(0);
}