164 lines
5.4 KiB
C
164 lines
5.4 KiB
C
#include "http_socket.h"
|
|
#include "../common/socket_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>
|
|
|
|
/**
|
|
* Gets the buffer size to allocate based on the given components.
|
|
*
|
|
* @param components An array of null-terminated C strings to measure the length of
|
|
* @param num_components The number of items in the components array.
|
|
*
|
|
* @return The buffer size to allocate, excluding the null terminator
|
|
*/
|
|
static size_t get_buffer_size(const char **components, int num_components) {
|
|
size_t buffer_size = 0;
|
|
for (int i = 0; i < num_components; i++) {
|
|
buffer_size += strlen(components[i]);
|
|
}
|
|
|
|
return buffer_size;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|