c-http-client/http_request.c
Ryan Febriansyah 48ba62686f initial commit
2023-06-18 17:56:32 +07:00

361 lines
10 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "http_request.h"
const char *http_method[] = {
"GET", "DELETE", "PUT", "POST"};
char *create_http_request(HttpRequest *request, const char *method)
{
size_t method_length = strlen(method);
size_t req_host = strlen(request->host);
size_t req_path = strlen(request->path);
size_t req_body = strlen(request->body);
// NOTE: currently, i'm still unsure how to do the calculation of the
// content length response. But, it must be calculated based on the
// HTTP protocol information (such as HTTP headers, method, host, path, etc)
// and pretty sure it was dynamic depend on each response that receive.
// particularly, here i just added the 100 characters to allocate or give
// the extra buffer in adequate response as much needed
size_t content_length = method_length + req_host + req_path + 100 + req_body;
char *http_request = (char *)malloc(content_length);
if (http_request == NULL)
{
fprintf(stderr, "Memory object allocation is failed \n");
return NULL;
}
snprintf(http_request, content_length, "%s Path: %s Host: %s Headers: %s \n", method, request->path, request->host, request->headers);
if (request->body != NULL)
{
strcat(http_request, request->body);
}
return http_request;
}
void http_response(const char *response)
{
int status_code = 0;
scanf(response, "HTTP Status Code: %d", &status_code);
// find the beginning of response body, if the response given
// is not null, then parsing the response based on the carriage
// return character and also print another response as the new line
const char *resp_body = strstr(response, "\r\n\r\n");
if (resp_body != NULL)
{
resp_body += 4;
printf("Response Body: %s \n", resp_body);
}
}
void send_http_request(HttpRequest *request, const char *method)
{
Session *ssl;
int create_socket = socket(AF_INET, SOCK_STREAM, 0);
if (create_socket == -1)
{
// returns a descriptor of the create socket protocol,
// if the returned value given -1, there's an error
fprintf(stderr, "Failed to initialize the create socket \n");
return;
}
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr(request->host);
server.sin_family = AF_INET;
server.sin_port = htons(443); // it may depends of HTTPS/HTTP protocol
// pasang ranjau wkwkw
printf("Full-path URL: %s\n", request->host);
clock_t start_time = clock();
if (connect(create_socket, (struct sockaddr *)&server, sizeof(server)) < 0)
{
fprintf(stderr, "Failed to connect the requested server %s\n", strerror(errno));
close_socket(create_socket);
return;
}
int total_of_http_methods = sizeof(http_method) / sizeof(http_method[0]);
int method_allowed = 0;
for (int i = 0; i < total_of_http_methods; i++)
{
if (strcmp(method, http_method[i]) == 0)
{
// cancel if the argument of HTTP method is returned value of 1
method_allowed = 1;
break;
}
}
if (!method_allowed)
{
fprintf(stderr, "Invalid HTTP Method: %s \n", method);
return;
}
if (!is_valid_fullpath_url(request->host, request->path))
{
return;
}
char *http_request = create_http_request(request, method);
if (http_request == NULL)
{
// automatically close the HTTP request function
// if there's no response after finished the request.
// eventually this logic same as like __close__ method
// just like in the Python's to avoiding memory leak
close_socket(create_socket);
return;
}
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
// create the SSL context before established the connection
// in order to requested to the HTTPS server
ssl->ctx = SSL_CTX_new(TLS_client_method());
if (ssl->ctx == NULL)
{
fprintf(stderr, "Failed to initialize the SSL context \n");
free(http_request);
close_socket(create_socket);
return;
}
ssl->ssl = SSL_new(ssl->ctx);
if (ssl->ssl == NULL)
{
fprintf(stderr, "Failed to create the SSL object \n");
free(http_request);
close_socket(create_socket);
return;
}
SSL_set_fd(ssl->ssl, create_socket);
if (SSL_connect(ssl->ssl) == -1)
{
fprintf(stderr, "Failed to established the SSL connection handshake \n");
free(http_request);
SSL_free(ssl->ssl);
SSL_CTX_free(ssl->ctx);
close_socket(create_socket);
return;
}
if (SSL_write(ssl->ssl, http_request, strlen(http_request)) < 0)
{
fprintf(stderr, "Failed when send the HTTP request \n");
free(http_request);
SSL_free(ssl->ssl);
SSL_CTX_free(ssl->ctx);
close_socket(create_socket);
return;
}
char response[4096];
if (SSL_read(ssl->ssl, response, sizeof(response)) < 0)
{
fprintf(stderr, "Failed to get the HTTP response \n");
free(http_request);
SSL_free(ssl->ssl);
SSL_CTX_free(ssl->ctx);
close_socket(create_socket);
return;
}
clock_t end_time = clock();
// http_response(response);
// int status_code = code(response);
// printf("HTTP status code is: %i \n", status_code);
double response_time = elapsed_time(start_time, end_time);
printf("HTTP Response Time %f \n", response_time);
free(http_request);
SSL_free(ssl->ssl);
SSL_CTX_free(ssl->ctx);
close_socket(create_socket);
return http_response(response);
}
int code(const char *response)
{
int status_code = 0;
sscanf(response, "HTTP/1.1 %i \n", &status_code);
return status_code;
}
char *headers(const char *response)
{
char *resp_headers = strstr(response, "\r\n\r\n");
if (resp_headers != NULL)
{
int headers_length = resp_headers - response;
char *size_of_headers = (char *)malloc(headers_length);
strncpy(size_of_headers, response, headers_length);
size_of_headers[headers_length] = '\0';
return size_of_headers;
}
return NULL;
}
double elapsed_time(clock_t start_time, clock_t end_time)
{
return (double)(end_time - start_time) / CLOCKS_PER_SEC;
}
Session *create_session()
{
// note: currently i tend to designed the Session method
// will working when associated with the prepare_request method
Session *session = (Session *)malloc(sizeof(Session));
if (session == NULL)
{
fprintf(stderr, "Create session request is failed \n");
return NULL;
}
int socket_descriptor = -1;
session->ctx = NULL;
session->ssl = NULL;
return session;
}
void close_session(Session *session)
{
// enforcing to destroy or removed all the Session, whether
// the connection it still established or still open
// practically speaking, i want to this close session would be
// called automatically just like using "with requests.Session()"
// in the Python's requests, but i don't know how to managed in C
int socket_descriptor = -1;
if (socket_descriptor != -1)
{
close_socket(socket_descriptor);
}
if (session->ssl != NULL)
{
SSL_shutdown(session->ssl);
SSL_free(session->ssl);
}
if (session->ctx != NULL)
{
SSL_CTX_free(session->ctx);
}
free(session);
}
void close_socket(int socket_descriptor)
{
close(socket_descriptor);
}
void prepare_request(Session *session, HttpRequest *request, const char *method, const char *headers)
{
// just like prepare_request() method in Python's requests
// add any additional information before sending a final HTTP
// request (such as append the new headers for auth or request body)
// NOTE: it's actually an optional method, it doesn't need to
// called it for the first time
size_t headers_length = strlen(request->headers) + strlen(headers);
char *merged_headers = (char *)malloc(headers_length);
request->headers = merged_headers;
if (session != NULL)
{
create_session(session);
send_http_request(request, method);
}
else
{
send_http_request(request, method);
}
free(merged_headers);
}
void get(HttpRequest *request)
{
send_http_request(request, "GET");
}
void post(HttpRequest *request)
{
send_http_request(request, "POST");
}
void del(HttpRequest *request)
{
send_http_request(request, "DELETE");
}
void put(HttpRequest *request)
{
send_http_request(request, "PUT");
}
int is_valid_fullpath_url(const char *host, const char *path)
{
if (host == NULL || strlen(host) == 0)
{
fprintf(stderr, "Invalid host argument \n");
return 0;
}
// since in C there's no built-in method to checking
// the path if its contain slash at the beginning, im
// assuming that every path shouldn't be had slash at first element
if (path == NULL || strlen(path) == 0 || path[0] != '/')
{
fprintf(stderr, "Invalid path argument \n");
return 0;
}
return 1;
}
int main()
{
const char *host = "https://jsonplaceholder.typicode.com/posts/2";
// const char *path = "/posts/1";
const char *body = "{}"; // try with empty body
const char *headers = "{}"; // try with empty headers
HttpRequest request = {host, body, headers};
Session *session = NULL;
// prepare_request(session, &request, "GET", "{}");
// example of using prepared request
// TODO: fix this segfaults error, i've assumed it was
// because inproper merged with additional headers
// prepare_request(session, &request, "GET", "Content-Type: application/json; charset=utf-8");
// or you may called based on HTTP method directly
get(&request);
// or primitively called the send_http_request methdo
send_http_request(&request, "GET");
return 0;
}