This repository has been archived on 2021-08-17. You can view files and clone it, but cannot push or open issues or pull requests.
unix/http/server.c

281 lines
9.4 KiB
C

#define _GNU_SOURCE ///< in order for optarg to be defined
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "shared/http.h"
#define LISTEN_BACKLOG 5 //< Define maximum size of the input queue
static int create_server_socket(uint16_t port, struct sockaddr_in incoming);
static char * setup_server_response(char* dir, char* ifile, char* request);
static char * get_file_contents(char* dir, char* filename);
int main(int argc, char *argv[]) {
check_opts_number(argc);
char *cport = "8080";
char *ifile = "index.html";
char *dir = NULL;
char *response; //< To hold the server response
int c;
/// Process the options
while((c = getopt(argc, argv, "p:i:h")) != -1) {
switch(c) {
case 'p':
cport = optarg;
break;
case 'i':
ifile = optarg;
break;
case 'h':
printf("Command usage:\n");
print_usage();
exit(EXIT_SUCCESS);
case '?':
print_usage();
exit(EXIT_FAILURE);
default:
abort();
}
}
/// Check if the required argument is there
if(argv[optind] == NULL) {
fprintf(stderr, "Mandatory argument 'doc_root' missing.\n");
print_usage();
exit(EXIT_FAILURE);
} else {
dir = argv[optind];
}
uint16_t port = atoi(cport); //< Convert the argument to integer
struct sockaddr_in incoming; //< Basic incoming config
incoming.sin_addr.s_addr = INADDR_ANY; //< Allow 0.0.0.0 to access the server
incoming.sin_port = htons(port); //< Set the port to work on
incoming.sin_family = AF_INET; //< Use IPv4
int sockfd = create_server_socket(port, incoming); //< Define a server socket
int current_session = 0;
socklen_t peer_addr_size = sizeof(struct sockaddr_in);
while(1) { //< Accept connections until terminated
/// Start accepting connections
current_session = accept(sockfd, (struct sockaddr*) &incoming, &peer_addr_size); //< Save the current session
if(current_session < 0) {
(void) fprintf(stderr, "ERROR: Could not start accepting connections\n");
close(current_session);
close(sockfd);
exit(EXIT_FAILURE);
} else {
char *request = malloc (sizeof (char) * __INT16_MAX__);
if(recv(current_session, request, strlen(request) - 1, 0) < 0) {
(void) fprintf(stderr, "ERROR: Could not receive connection from client\n");
close(current_session);
close(sockfd);
exit(EXIT_FAILURE);
}
/// Setup the server response
response = setup_server_response(dir, ifile, request);
/// Send the response to the client
if(send(current_session, response, strlen(response) + 1, 0) < 0) {
(void) fprintf(stderr, "ERROR: Could not send the response to the client\n");
close(current_session);
close(sockfd);
exit(EXIT_FAILURE);
}
}
}
close(current_session);
close(sockfd);
exit(EXIT_SUCCESS);
}
static void print_usage(void) {
printf("server [-p PORT] [ -i INDEX ] DOC_ROOT\n");
}
/**
* @brief Function to check the number of arguments
* @details Checks the number of arguments supplied to the command line.
* If the arguments are less than the required or more than possible, it exits
* @param argc - Number of arguments obtained from the command line
* @return none
*
**/
static void check_opts_number(int argc) {
if(argc <= 1) {
fprintf(stderr, "At least one argument expected.\n");
print_usage();
exit(EXIT_FAILURE);
} else if(argc > 6) {
fprintf(stderr, "Too many arguments supplied.\n");
print_usage();
exit(EXIT_FAILURE);
}
}
/**
* @brief Function to create a socket using a default configuration for a server
* @details Creates a socket using a default configuration for a server
* If there was no error, it returns the socket
* If there was an error, it exits and displays an error message
* @param port - the port to bind the server to
* incoming - basic incoming sockaddr config
* @return sockfd - The socket obtained from the socket creation
**/
static int create_server_socket(uint16_t port, struct sockaddr_in incoming) {
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //< Define socket with default configuration
if(sockfd < 0) {
(void) fprintf(stderr, "ERROR: Socket creation failed.\n");
exit(EXIT_FAILURE);
}
/// Bind to a port
if(bind(sockfd, (struct sockaddr*) &incoming, sizeof(struct sockaddr_in)) < 0) {
(void) fprintf(stderr, "ERROR: Could not bind server to port %d!\n", port);
close(sockfd);
exit(EXIT_FAILURE);
}
/// Start listening on the socket
if(listen(sockfd, LISTEN_BACKLOG) < 0)
{
(void) fprintf(stderr, "ERROR: Could not start listening\n");
close(sockfd);
exit(EXIT_FAILURE);
} else {
(void) fprintf(stdout, "Listening on port %d...\n", port);
}
return sockfd;
}
/**
* @brief Function to create the HTTP response
* @details Gets the client request
* If the server receives an invalid requests, a 400 header is build and the function adds it to response
* If a GET request is received, the function tries to open the resource:
* - if it could open it, it builds a 200 header and the file contents as body and adds them to response
* - if it could not open it, it builds a 404 header and adds it to response
* If anything other than a GET request is received, a 501 header is build and the function adds it to response
* At the end, it returns the server response with header and body (if available)
* @param dir - the directory of the file
* ifile - the name of the indexfile
* request - the request of the client
* @return response - A header with the status code and a body (if available)
**/
static char * setup_server_response(char* dir, char* ifile, char* request) {
char *response = malloc (sizeof (char) * __INT16_MAX__);
char method[4];
strncpy(method, request, 3); //< Get the request type
method[3] = 0; //< Add terminating string
char header[__INT8_MAX__ + 100];
if(strcmp(method, "GET") == 0) {
char *body = NULL;
char *delim = strstr(request, "/");
char *req_file = strtok(delim, " "); //< Get the requested file
if(strlen(req_file) == 1) {
body = get_file_contents(dir, ifile);
}
else {
char *rest = strstr(request, "HTTP/1.1");
rest = strtok(rest, "\r\n");
if(rest == NULL) {
strcpy(
header,
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"
); //< Build the 400 header
strcat(response, header);
} else {
body = get_file_contents(dir, req_file + 1);
(void) fprintf(stdout, "GET: %s\n", req_file);
}
}
if(body != NULL) {
time_t current_time;
time(&current_time);
struct tm * tm = gmtime(&current_time);
char time_string[50];
strftime(time_string, 50, "%a, %d %b %y %H:%M:%S %Z", tm);
snprintf(
header,
sizeof header,
"HTTP/1.1 200 OK\r\nDate: %s\r\nContent-Length: %ld\r\nConnection: close\r\n\r\n",
time_string, strlen(body)
); //< Build the 200 header
strcat(response, header);
strcat(response, body);
} else {
strcpy(
header,
"HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"
); //< Build the 404 header
strcat(response, header);
}
} else {
strcpy(
header,
"HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n"
); //< Build 501 the header
strcat(response, header);
}
return response;
}
/**
* @brief Function to take the contents of a specified file
* @details Gets the contents of the file specified
* If there is no file, it returns NULL
* If the file is empty, it returns " "
* If the file is not empty, it returns its contents
* @param dir - the directory of the file
* filename - the name of the file
* @return res - The contents of the file, formatted as a string
**/
static char * get_file_contents(char *dir, char *filename) {
char path[strlen(dir) + strlen(filename) + 100]; //< set the variable for the path with a reasonable length
snprintf(path, sizeof path, "%s%s", dir, filename);
FILE *file = NULL;
file = fopen(path, "r");
if(file == NULL) {
return NULL;
} else {
char *res = 0;
fseek(file, 0, SEEK_END); //< Set position to end of stream
int file_length = ftell(file); //< Get file length
fseek(file, 0, SEEK_SET); //< Set the position to begining of stream
res = malloc(file_length); //< Allocate memory for the file contents
fread(res, 1, file_length, file); //< Read the contents into res
return res;
}
}