cs-3516-assignment-1/client/http_socket.c

148 lines
4.9 KiB
C

#include "http_socket.h"
#include "../common/socket_helper.h"
#include "../common/buffer_helper.h"
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
/**
* Build a basic request into an http_message
*
* @param method The HTTP method to use
* @param host The host requested
* @param path The path on the host requested
* @param port The port of the host requested
*
* @return A http_message - note that all host, path, and contents must all be freed using free_basic_request
*/
struct http_message build_basic_request(const char *method, const char *host, const char *path, int port) {
struct http_message message;
int host_length = strlen(host) + 1;
message.address = malloc(host_length * sizeof(char));
strcpy(message.address, host);
message.port = port;
int path_length = strlen(path) + 1;
message.path = malloc(path_length * sizeof(char));
strcpy(message.path, path);
const char *request_line_components[] = {method, " ", path, " ", HTTP_VERSION, "\r\n"};
const char *host_header[] = {"Host: ", host, "\r\n"};
// Add the size of the buffers, plus space for the trailing carriage return and the null term.
int message_buffer_size = get_buffer_size(request_line_components, 6) + get_buffer_size(host_header, 3) + 2 + 1;
message.contents = malloc(message_buffer_size * sizeof(char));
sprintf(message.contents, "%s %s %s\r\nHost: %s\r\n\r\n", method, path, HTTP_VERSION, host);
return message;
}
/**
* Free a request allocated by build_basic_request
*
* @param req A request allocated by build_basic_request
*/
void free_basic_request(struct http_message req) {
free(req.address);
free(req.path);
free(req.contents);
}
/**
* Get the address info for a given hostname and port.
*
* @param hostname The hostname to read
* @param port The port to get the address info on
* @param info A pointer to a location where getaddrinfo can store data
*
* @return The result of getadddrinfo. See `man getaddrinfo` for details.
*/
static int build_addr_info(const char *hostname, int port, struct addrinfo **info) {
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
// Get the number of chars in the port
int port_length = floor(log10(port)) + 1;
char *port_string = malloc((port_length + 1) * sizeof(char));
sprintf(port_string, "%d", port);
int addr_status = getaddrinfo(hostname, port_string, &hints, info);
free(port_string);
return addr_status;
}
static long long get_rtt(struct timeval pre_connect_time, struct timeval post_connect_time) {
long long start_time = pre_connect_time.tv_sec + pre_connect_time.tv_usec/1000;
long long end_time = post_connect_time.tv_sec + post_connect_time.tv_usec/1000;
return end_time - start_time;
}
/**
* Send a given HTTP request
*
* @param req The HTTP request to send
* @param res_info A pointer to an already allocated response_info. res_info.contents must be freed by the caller.
*
* @return A socket_result representing the status of the response.
*/
enum socket_result send_request(struct http_message req, struct response_info *res_info) {
struct addrinfo *addr_info;
int addr_status = build_addr_info(req.address, req.port, &addr_info);
if (addr_status != 0) {
return RESULT_READ_ERROR;
}
int socket_fd = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
if (socket_fd < 0) {
return RESULT_READ_ERROR;
}
struct timeval pre_connect_time;
struct timeval post_connect_time;
// Initialize the time before beginning our connection
int start_time_result = gettimeofday(&pre_connect_time, NULL);
if (start_time_result != 0) {
close(socket_fd);
return RESULT_PROCESSING_ERROR;
}
//Connect to the socket.
int connection_status = connect(socket_fd, addr_info->ai_addr, addr_info->ai_addrlen);
if (connection_status != 0) {
close(socket_fd);
return RESULT_READ_ERROR;
}
//Get the end time of the connection
int end_time_result = gettimeofday(&post_connect_time, NULL);
if (end_time_result != 0) {
close(socket_fd);
return RESULT_PROCESSING_ERROR;
}
// Calculcate the RTT based on how long it took to connect to the server.
res_info->rtt = get_rtt(pre_connect_time, post_connect_time);
// Send and process the request.
send(socket_fd, req.contents, strlen(req.contents), 0);
struct http_message res = {};
enum socket_result result = get_all_remote_parts(socket_fd, TYPE_CLIENT, &res);
res_info->contents = res.contents;
close(socket_fd);
free(res.start_line);
// It is valid for there to be no headers.
if (res.headers != NULL) {
free_headers(res.headers);
}
freeaddrinfo(addr_info);
return result;
}