From 400c3916ff407e05276b5a323d945035efede0cb Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Tue, 6 Nov 2018 19:45:29 +0100 Subject: [PATCH] Add initial server functionality and clean up --- http/README.md | 7 +- http/client.c | 86 +++++++++---------- http/server.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++ mygrep/mygrep.c | 1 - 4 files changed, 270 insertions(+), 47 deletions(-) diff --git a/http/README.md b/http/README.md index 42079d9..c2c00e3 100644 --- a/http/README.md +++ b/http/README.md @@ -15,6 +15,11 @@ the URL. The transmitted content of that file is written to `stdout`, to a file ## server -Not implemented yet +The server takes a document root as input, reads it and serves it on the port specified(`8080` by default). If the `-i` flag is specified, the server uses its value as an index file. If not, it takes index.html as a default. The server supports **ONLY** GET requests. If it receives another request type it will return a response status **501** (Not implemented). If the file requested is not found, the server returns a response status **404** (Not found). + + SYNOPSIS + server [-p PORT] [-i INDEX] DOC_ROOT + EXAMPLE + server -p 1280 -i index.html ./public/ **Note: The description is from the task I got from TU.** \ No newline at end of file diff --git a/http/client.c b/http/client.c index 9be669e..864a07c 100644 --- a/http/client.c +++ b/http/client.c @@ -56,7 +56,6 @@ int main(int argc, char *argv[]) { printf("Command usage:\n"); print_usage(); exit(EXIT_SUCCESS); - break; case '?': print_usage(); exit(EXIT_FAILURE); @@ -116,6 +115,8 @@ int main(int argc, char *argv[]) { process_response(reply, ofile, dir); freeaddrinfo(config); //< Free the addrinfo struct + + exit(EXIT_SUCCESS); //< Exit with success } static void print_usage(void) { @@ -177,7 +178,7 @@ static struct addrinfo* get_host_info(const char *hostname, char *port) { } /** - * @brief Function to create a socker using the supplied configuration + * @brief Function to create a socket using the supplied configuration * @details Creates a socket using the supplied configuration * If there was no error, it returns the socket * If there was an error, it exits and displays an error message @@ -185,7 +186,7 @@ static struct addrinfo* get_host_info(const char *hostname, char *port) { * @return sockfd - The socket obtained from the socket creation **/ static int create_socket(struct addrinfo* config) { - int sockfd = socket(config->ai_family, config->ai_socktype, config->ai_protocol); + int sockfd = socket(config -> ai_family, config -> ai_socktype, config -> ai_protocol); if(sockfd < 0) { (void) fprintf(stderr, "ERROR: Socket creation failed.\n"); @@ -210,7 +211,7 @@ static void process_response(char reply[sizeof(int32_t)], char *ofile, char *dir char *http_version = "HTTP/1.1"; if(strncmp(reply, http_version, strlen(http_version)) == 0) { //< Check if the remote gives a correct response header char cresponse_code[3]; //< Array for the response code. Response codes are always 3 digits long - strncpy(cresponse_code ,strstr(reply, "HTTP/1.1 ") + sizeof(http_version) + 1, 3); //< Get the response code as string. + strncpy(cresponse_code, strstr(reply, "HTTP/1.1 ") + sizeof(http_version) + 1, 3); //< Get the response code as string. int response_code = strtol(cresponse_code, NULL, 10); //< Get the response code as integer @@ -219,56 +220,51 @@ static void process_response(char reply[sizeof(int32_t)], char *ofile, char *dir exit(EXIT_FAILURE); } else if (response_code != 200) { printf("%s\n", strtok(reply, "\r\n")); //< Get the first line - exit(EXIT_SUCCESS); - } + } else { + char *body = strstr(reply, "\r\n\r\n") + 4; + if(ofile == NULL && dir == NULL) printf("%s", body); //< Print the body of the response + else { + if(ofile != NULL) { + FILE *out = NULL; + out = fopen(ofile, "w"); - char *body = strstr(reply, "\r\n\r\n") + 4; - if(ofile == NULL && dir == NULL) printf("%s", body); //< Print the body of the response - else { - if(ofile != NULL) { - FILE *out = NULL; - out = fopen(ofile, "w"); + if(out == NULL) { + (void) fprintf(stderr, "ERROR: Could not open ./%s for writing.", ofile); + exit(EXIT_FAILURE); + } else { + fprintf(out, "%s", body); + fclose(out); + printf("File ./%s successfully written.\n", ofile); + } + } else if(dir != NULL) { + FILE *out = NULL; - if(out == NULL) { - (void) fprintf(stderr, "ERROR: Could not open ./%s for writing.", ofile); - exit(EXIT_FAILURE); - } else { - fprintf(out, "%s", body); - fclose(out); - printf("File ./%s successfully written.\n", ofile); - exit(EXIT_SUCCESS); - } - } else if(dir != NULL) { - FILE *out = NULL; - - char path[sizeof(dir) + 100]; - if(dir[strlen(dir) - 1] == '/') - snprintf(path, sizeof path, "%sresult.html", dir); - else - snprintf(path, sizeof path, "%s/result.html", dir); - - out = fopen(path, "w"); //< Open /result.html for writing - - if(out == NULL) { + char path[sizeof(dir) + 100]; if(dir[strlen(dir) - 1] == '/') - (void) fprintf(stderr, "ERROR: Could not open %sresult.html for writing.\n", dir); + snprintf(path, sizeof path, "%sresult.html", dir); else - (void) fprintf(stderr, "ERROR: Could not open %s/result.html for writing.\n", dir); - exit(EXIT_FAILURE); - } else { - fprintf(out, "%s", body); - fclose(out); + snprintf(path, sizeof path, "%s/result.html", dir); - if(dir[strlen(dir) - 1] == '/') - printf("File %sresult.html successfully written.\n", dir); - else - printf("File %s/result.html successfully written.\n", dir); + out = fopen(path, "w"); //< Open /result.html for writing - exit(EXIT_SUCCESS); + if(out == NULL) { + if(dir[strlen(dir) - 1] == '/') + (void) fprintf(stderr, "ERROR: Could not open %sresult.html for writing.\n", dir); + else + (void) fprintf(stderr, "ERROR: Could not open %s/result.html for writing.\n", dir); + exit(EXIT_FAILURE); + } else { + fprintf(out, "%s", body); + fclose(out); + + if(dir[strlen(dir) - 1] == '/') + printf("File %sresult.html successfully written.\n", dir); + else + printf("File %s/result.html successfully written.\n", dir); + } } } } - } else { fprintf(stderr, "ERROR: Protocol error.\n"); exit(EXIT_FAILURE); diff --git a/http/server.c b/http/server.c index e69de29..5b61b9e 100644 --- a/http/server.c +++ b/http/server.c @@ -0,0 +1,223 @@ +#define _GNU_SOURCE ///< in order for optarg to be defined +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 * get_file_contents(char *dir, char *file); + +int main(int argc, char *argv[]) { + check_opts_number(argc); + + char *cport = "8080"; + char *ifile = "index.html"; + char *dir = NULL; + char response[sizeof(int32_t)]; //< 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 + + socklen_t peer_addr_size = sizeof(struct sockaddr_in); + + /// Start accepting connections + int 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(sockfd); + exit(EXIT_FAILURE); + } else { + char request[sizeof(int32_t)]; + + 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); + } else { + char method[4]; + strncpy(method, request, 4); //< Get the request type + method[3] = 0; //< Add terminating char + + if(strcmp(method, "GET") == 0) { + (void) fprintf(stdout, "Got here\n"); + close(current_session); + close(sockfd); + } else { + fprintf(stderr, "ERROR: Unhandled HTTP request method received\n"); + char header[sizeof(int8_t) + 100]; + time_t t = time(NULL); + struct tm tm = *localtime(&t); + snprintf( + header, + sizeof header, + "HTTP/1.1 501 Not Implemented\r\nDate: %d, %d %d %d %d:%d:%d %s\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", + tm.tm_wday, tm.tm_mday, tm.tm_mon, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone + ); //< Build the header + + puts(header); + strncpy(response, header, sizeof(header)); + } + close(current_session); + close(sockfd); + } + } + + /// 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 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 = strcpy(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 + + if(res != 0) { + fread(res, 1, file_length, file); //< Read the contents into res + } else { + return " "; + } + + return res; + } +} diff --git a/mygrep/mygrep.c b/mygrep/mygrep.c index fa7d47b..9e1970b 100644 --- a/mygrep/mygrep.c +++ b/mygrep/mygrep.c @@ -59,7 +59,6 @@ int main(int argc, char *argv[]) { printf("Command usage:\n"); print_usage(); exit(EXIT_SUCCESS); - break; case '?': print_usage(); exit(EXIT_FAILURE);