361 lines
10 KiB
C
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;
|
|
}
|