From 2f552e5ad43201977a2b94381f97f47d01371ec1 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Mon, 5 Nov 2018 17:46:15 +0100 Subject: [PATCH] Add working http client --- .gitignore | 4 ++ http/Makefile | 6 +-- http/client.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 142 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index cdc109e..27e7ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .vscode/ *.out *.o +*.html +!public/*.html +http/client +http/server diff --git a/http/Makefile b/http/Makefile index 3f141d8..9abf385 100644 --- a/http/Makefile +++ b/http/Makefile @@ -21,7 +21,7 @@ install: cp $(TARGET_2) /usr/local/bin/myhttp-$(TARGET_2) clean: - $(RM) $(TARGET_1).o $(RM) $(TARGET_1) - $(RM) $(TARGET_2).o - $(RM) $(TARGET_2) \ No newline at end of file + $(RM) $(TARGET_2) + $(RM) *.o + $(RM) *.html \ No newline at end of file diff --git a/http/client.c b/http/client.c index 02374fa..9be669e 100644 --- a/http/client.c +++ b/http/client.c @@ -28,6 +28,8 @@ #include "shared/http.h" static struct addrinfo* get_host_info(const char *hostname, char *port); +static int create_socket(struct addrinfo* config); +static void process_response(char reply[sizeof(int32_t)], char *ofile, char *dir); int main(int argc, char *argv[]) { check_opts_number(argc); @@ -79,13 +81,41 @@ int main(int argc, char *argv[]) { url = argv[optind]; } - const char *full_url = strstr(url, "http://") + 7; //< the full url starts after http:// - const char *path = strstr(full_url, "/"); - const char *hostname = "google.com"; //< TODO: Change this later + char *base_url = strstr(url, "http://") + 7; //< the base url starts after http:// - struct addrinfo* res = get_host_info(hostname, port); //< Check if the address is resolvable + const char *path = strstr(base_url, "/"); + if(path == NULL) path = "/"; //< Set the root path if there is none - freeaddrinfo(res); //< Free the addrinfo struct + const char *hostname = strtok(base_url, ";/?:@=&"); //< Get the FQDN of the remote + + struct addrinfo* config = get_host_info(hostname, port); //< Check if the address is resolvable + + char header[strlen(base_url) + 100]; //< Create a variable to store the header + + snprintf(header, sizeof header, "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", path, hostname); //< Build the header + + int sockfd = create_socket(config); + + if (connect(sockfd, config -> ai_addr, config -> ai_addrlen) == -1) { //< Connect to the host + (void) fprintf(stderr, "ERROR: Failure connecting to the server.\n"); + exit(EXIT_FAILURE); + } + + if((send(sockfd, header, strlen(header), 0)) < 0) { //< Send the GET request + (void) fprintf(stderr, "ERROR: Failure sending the request to the server.\n"); + exit(EXIT_FAILURE); + } + + char reply[sizeof(int32_t)]; //< Set the variable to hold the server response. Set to a big length + + if((recv(sockfd, reply, strlen(reply) - 1, 0)) < 0) { //< Receive response from the remote (-1 for the trailing terminating char) + (void) fprintf(stderr, "ERROR: Did not receive response from the remote.\n"); + exit(EXIT_FAILURE); + } + + process_response(reply, ofile, dir); + + freeaddrinfo(config); //< Free the addrinfo struct } static void print_usage(void) { @@ -115,7 +145,7 @@ static void check_opts_number(int argc) { /** * @brief Function to check if the address is resolvable by the client * @details Checks if the address is resolvable by the client. - * If the address is resolvable, it writes the result to res + * If the address is resolvable, it returns the result * If the address is not resolvable, it exits with an error * @param * hostname - The address to be resolved @@ -138,7 +168,6 @@ static struct addrinfo* get_host_info(const char *hostname, char *port) { (void) fprintf(stderr, "ERROR: %s\n", gai_strerror(err)); exit(EXIT_FAILURE); //< Exit if there is an error } - if(res == NULL) { (void) fprintf(stderr, "ERROR: Could not resolve address %s\n", hostname); exit(EXIT_FAILURE); //< Exit if the address is not resolved @@ -146,3 +175,102 @@ static struct addrinfo* get_host_info(const char *hostname, char *port) { return res; } + +/** + * @brief Function to create a socker 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 + * @param config - The the configuration to be used for the socket creation + * @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); + + if(sockfd < 0) { + (void) fprintf(stderr, "ERROR: Socket creation failed.\n"); + exit(EXIT_FAILURE); + } + + return sockfd; +} + +/** + * @brief Function to process the response of the remote host + * @details Processes the reply according to its' content and the ofile and dir parameters + * The function checks the response code does the following: + * - if the reponse code is invalid, it exits with an error + * - if the response code is different from 200, it prints the whole response and exits with status 3 + * - if the response code is 200, it prints it to the console or saves it as a file + * If there was an error, it exits and displays an error message + * @param config - The the configuration to be used for the socket creation + * @return null + **/ +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. + + int response_code = strtol(cresponse_code, NULL, 10); //< Get the response code as integer + + if(response_code == 0) { + fprintf(stderr, "ERROR: Protocol error.\n"); + exit(EXIT_FAILURE); + } else if (response_code != 200) { + printf("%s\n", strtok(reply, "\r\n")); //< Get the first line + exit(EXIT_SUCCESS); + } + + 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); + 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) { + 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); + + exit(EXIT_SUCCESS); + } + } + } + + } else { + fprintf(stderr, "ERROR: Protocol error.\n"); + exit(EXIT_FAILURE); + } +}