#ifndef RHTTP_H
#define RHTTP_H
#include "rio.h"
#include "rmalloc.h"
#include "rstring.h"
#include "rtemp.h"
#include "rtime.h"
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define BUFF_SIZE 8096
#define RHTTP_MAX_CONNECTIONS 100
int rhttp_opt_error = 1;
int rhttp_opt_warn = 1;
int rhttp_opt_info = 1;
int rhttp_opt_port = 8080;
int rhttp_opt_debug = 0;
int rhttp_opt_request_logging = 0;
int rhttp_sock = 0;
int rhttp_opt_buffered = 0;
int rhttp_c = 0;
int rhttp_c_mutex_initialized = 0;
pthread_mutex_t rhttp_c_mutex;
char rhttp_opt_host[1024] = "0.0.0.0";
unsigned int rhttp_connections_handled = 0;
typedef struct rhttp_header_t {
char *name;
char *value;
struct rhttp_header_t *next;
} rhttp_header_t;
typedef struct rhttp_request_t {
int c;
int closed;
bool keep_alive;
nsecs_t start;
char *raw;
char *line;
char *body;
char *method;
char *path;
char *version;
void *context;
unsigned int bytes_received;
rhttp_header_t *headers;
} rhttp_request_t;
char *rhttp_current_timestamp() {
time_t current_time;
time(&current_time);
struct tm *local_time = localtime(&current_time);
static char time_string[100];
time_string[0] = 0;
strftime(time_string, sizeof(time_string), "%Y-%m-%d %H:%M:%S", local_time);
return time_string;
}
void rhttp_logs(const char *prefix, const char *level, const char *format, va_list args) {
char buf[strlen(format) + BUFSIZ + 1];
buf[0] = 0;
sprintf(buf, "%s%s %s %s\e[0m", prefix, rhttp_current_timestamp(), level, format);
vfprintf(stdout, buf, args);
}
void rhttp_log_info(const char *format, ...) {
if (!rhttp_opt_info)
return;
va_list args;
va_start(args, format);
rhttp_logs("\e[32m", "INFO ", format, args);
va_end(args);
}
void rhttp_log_debug(const char *format, ...) {
if (!rhttp_opt_debug)
return;
va_list args;
va_start(args, format);
if (rhttp_opt_debug)
rhttp_logs("\e[33m", "DEBUG", format, args);
va_end(args);
}
void rhttp_log_warn(const char *format, ...) {
if (!rhttp_opt_warn)
return;
va_list args;
va_start(args, format);
rhttp_logs("\e[34m", "WARN ", format, args);
va_end(args);
}
void rhttp_log_error(const char *format, ...) {
if (!rhttp_opt_error)
return;
va_list args;
va_start(args, format);
rhttp_logs("\e[35m", "ERROR", format, args);
va_end(args);
}
void http_request_init(rhttp_request_t *r) {
r->raw = NULL;
r->line = NULL;
r->body = NULL;
r->method = NULL;
r->path = NULL;
r->version = NULL;
r->start = 0;
r->headers = NULL;
r->bytes_received = 0;
r->closed = 0;
}
void rhttp_free_header(rhttp_header_t *h) {
if (!h)
return;
rhttp_header_t *next = h->next;
free(h->name);
free(h->value);
free(h);
if (next)
rhttp_free_header(next);
}
void rhttp_rhttp_free_headers(rhttp_request_t *r) {
if (!r->headers)
return;
rhttp_free_header(r->headers);
r->headers = NULL;
}
rhttp_header_t *rhttp_parse_headers(rhttp_request_t *s) {
int first = 1;
char *body = strdup(s->body);
char *body_original = body;
while (body && *body) {
char *line = __strtok_r(first ? body : NULL, "\r\n", &body);
if (!line)
break;
rhttp_header_t *h = (rhttp_header_t *)malloc(sizeof(rhttp_header_t));
h->name = NULL;
h->value = NULL;
h->next = NULL;
char *name = __strtok_r(line, ": ", &line);
first = 0;
if (!name) {
rhttp_free_header(h);
break;
}
h->name = strdup(name);
char *value = __strtok_r(NULL, "\r\n", &line);
if (!value) {
rhttp_free_header(h);
break;
}
h->value = value ? strdup(value + 1) : strdup("");
h->next = s->headers;
s->headers = h;
}
free(body_original);
return s->headers;
}
void rhttp_free_request(rhttp_request_t *r) {
if (r->raw) {
free(r->raw);
free(r->body);
free(r->method);
free(r->path);
free(r->version);
rhttp_rhttp_free_headers(r);
}
free(r);
}
long rhttp_header_get_long(rhttp_request_t *r, const char *name) {
rhttp_header_t *h = r->headers;
while (h) {
if (!strcmp(h->name, name))
return strtol(h->value, NULL, 10);
h = h->next;
}
return -1;
}
char *rhttp_header_get_string(rhttp_request_t *r, const char *name) {
rhttp_header_t *h = r->headers;
while (h) {
if (!strcmp(h->name, name))
return h->value && *h->value ? h->value : NULL;
h = h->next;
}
return NULL;
}
void rhttp_print_header(rhttp_header_t *h) { rhttp_log_debug("Header: <%s> \"%s\"\n", h->name, h->value); }
void rhttp_print_headers(rhttp_header_t *h) {
while (h) {
rhttp_print_header(h);
h = h->next;
}
}
void rhttp_print_request_line(rhttp_request_t *r) { rhttp_log_info("%s %s %s\n", r->method, r->path, r->version); }
void rhttp_print_request(rhttp_request_t *r) {
rhttp_print_request_line(r);
if (rhttp_opt_debug)
rhttp_print_headers(r->headers);
}
void rhttp_close(rhttp_request_t *r) {
if (!r)
return;
if (!r->closed)
close(r->c);
rhttp_free_request(r);
}
rhttp_request_t *rhttp_parse_request(int s) {
rhttp_request_t *request = (rhttp_request_t *)malloc(sizeof(rhttp_request_t));
http_request_init(request);
char buf[BUFF_SIZE] = {0};
request->c = s;
int breceived = 0;
while (!rstrendswith(buf, "\r\n\r\n")) {
int chunk_size = read(s, buf + breceived, 1);
if (chunk_size <= 0) {
close(request->c);
request->closed = 1;
return request;
}
breceived += chunk_size;
}
if (breceived <= 0) {
close(request->c);
request->closed = 1;
return request;
}
buf[breceived] = '\0';
char *original_buf = buf;
char *b = original_buf;
request->raw = strdup(b);
b = original_buf;
char *line = strtok(b, "\r\n");
b = original_buf;
char *body = b + strlen(line) + 2;
request->body = strdup(body);
b = original_buf;
char *method = strtok(b, " ");
char *path = strtok(NULL, " ");
char *version = strtok(NULL, " ");
request->bytes_received = breceived;
request->line = line;
request->start = nsecs();
request->method = strdup(method);
request->path = strdup(path);
request->version = strdup(version);
request->headers = NULL;
request->keep_alive = false;
if (rhttp_parse_headers(request)) {
char *keep_alive_string = rhttp_header_get_string(request, "Connection");
if (keep_alive_string && !strcmp(keep_alive_string, "keep-alive")) {
request->keep_alive = 1;
}
}
return request;
}
void rhttp_close_server() {
close(rhttp_sock);
close(rhttp_c);
printf("Connections handled: %d\n", rhttp_connections_handled);
printf("Gracefully closed\n");
exit(0);
}
size_t rhttp_send_drain(int s, void *tsend, size_t to_send_len) {
if (to_send_len == 0 && *(unsigned char *)tsend) {
to_send_len = strlen(tsend);
}
unsigned char *to_send = (unsigned char *)malloc(to_send_len);
unsigned char *to_send_original = to_send;
memcpy(to_send, tsend, to_send_len);
// to_send[to_send_len] = '\0';
long bytes_sent = 0;
long bytes_sent_total = 0;
while (1) {
bytes_sent = send(s, to_send + bytes_sent_total, to_send_len - bytes_sent_total, 0);
if (bytes_sent <= 0) {
bytes_sent_total = 0;
break;
}
bytes_sent_total += bytes_sent;
if (bytes_sent_total == (long)to_send_len) {
break;
} else if (!bytes_sent) {
bytes_sent_total = 0;
// error
break;
} else {
rhttp_log_info("Extra send of %d/%d bytes.\n", bytes_sent_total, to_send_len);
}
}
free(to_send_original);
return bytes_sent_total;
}
typedef int (*rhttp_request_handler_t)(rhttp_request_t *r);
void rhttp_serve(const char *host, int port, int backlog, int request_logging, int request_debug, rhttp_request_handler_t handler,
void *context) {
signal(SIGPIPE, SIG_IGN);
rhttp_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(host ? host : "0.0.0.0");
rhttp_opt_debug = request_debug;
rhttp_opt_request_logging = request_logging;
int opt = 1;
setsockopt(rhttp_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(rhttp_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
printf("Binding error\n");
exit(1);
}
listen(rhttp_sock, backlog);
while (1) {
struct sockaddr_in client_addr;
int addrlen = sizeof(client_addr);
rhttp_c = accept(rhttp_sock, (struct sockaddr *)&client_addr, (socklen_t *)&addrlen);
rhttp_connections_handled++;
while (true) {
rhttp_request_t *r = rhttp_parse_request(rhttp_c);
r->context = context;
if (!r->closed) {
if (!handler(r) && !r->closed) {
rhttp_close(r);
}
}
if (!r->keep_alive && !r->closed) {
rhttp_close(r);
} else if (r->keep_alive && !r->closed) {
}
if (r->closed) {
break;
}
rhttp_free_request(r);
}
}
}
unsigned int rhttp_calculate_number_char_count(unsigned int number) {
unsigned int width = 1;
unsigned int tcounter = number;
while (tcounter / 10 >= 1) {
tcounter = tcounter / 10;
width++;
}
return width;
}
int rhttp_file_response(rhttp_request_t *r, char *path) {
if (!*path)
return 0;
FILE *f = fopen(path, "rb");
if (f == NULL)
return 0;
size_t file_size = rfile_size(path);
char response[1024] = {0};
char content_type_header[100] = {0};
char *ext = strstr(path, ".");
char *text_extensions = ".h,.c,.html";
if (strstr(text_extensions, ext)) {
sprintf(content_type_header, "Content-Type: %s\r\n", "text/html");
}
sprintf(response, "HTTP/1.1 200 OK\r\n%sContent-Length:%ld\r\n\r\n", content_type_header, file_size);
if (!rhttp_send_drain(r->c, response, 0)) {
rhttp_log_error("Error sending file: %s\n", path);
}
size_t bytes = 0;
size_t bytes_sent = 0;
unsigned char file_buff[1024];
while ((bytes = fread(file_buff, sizeof(char), sizeof(file_buff), f))) {
if (!rhttp_send_drain(r->c, file_buff, bytes)) {
rhttp_log_error("Error sending file during chunking: %s\n", path);
}
bytes_sent += bytes;
}
if (bytes_sent != file_size) {
rhttp_send_drain(r->c, file_buff, file_size - bytes_sent);
}
close(r->c);
fclose(f);
return 1;
};
int rhttp_file_request_handler(rhttp_request_t *r) {
char *path = r->path;
while (*path == '/' || *path == '.')
path++;
if (strstr(path, "..")) {
return 0;
}
return rhttp_file_response(r, path);
};
unsigned int counter = 100000000;
int rhttp_counter_request_handler(rhttp_request_t *r) {
if (!strncmp(r->path, "/counter", strlen("/counter"))) {
counter++;
unsigned int width = rhttp_calculate_number_char_count(counter);
char to_send2[1024] = {0};
sprintf(to_send2,
"HTTP/1.1 200 OK\r\nContent-Length: %d\r\nConnection: "
"close\r\n\r\n%d",
width, counter);
rhttp_send_drain(r->c, to_send2, 0);
close(r->c);
return 1;
}
return 0;
}
int rhttp_root_request_handler(rhttp_request_t *r) {
if (!strcmp(r->path, "/")) {
char to_send[1024] = {0};
sprintf(to_send, "HTTP/1.1 200 OK\r\nContent-Length: 3\r\nConnection: "
"close\r\n\r\nOk!");
rhttp_send_drain(r->c, to_send, 0);
close(r->c);
return 1;
}
return 0;
}
int rhttp_error_404_handler(rhttp_request_t *r) {
char to_send[1024] = {0};
sprintf(to_send, "HTTP/1.1 404 Document not found\r\nContent-Length: "
"0\r\nConnection: close\r\n\r\n");
rhttp_send_drain(r->c, to_send, 0);
close(r->c);
return 1;
}
int rhttp_default_request_handler(rhttp_request_t *r) {
if (rhttp_opt_debug || rhttp_opt_request_logging)
rhttp_print_request(r);
if (rhttp_counter_request_handler(r)) {
// Counter handler
rhttp_log_info("Counter handler found for: %s\n", r->path);
} else if (rhttp_root_request_handler(r)) {
// Root handler
rhttp_log_info("Root handler found for: %s\n", r->path);
} else if (rhttp_file_request_handler(r)) {
rhttp_log_info("File %s sent\n", r->path);
} else if (rhttp_error_404_handler(r)) {
rhttp_log_warn("Error 404 for: %s\n", r->path);
// Error handler
} else {
rhttp_log_warn("No handler found for: %s\n", r->path);
close(rhttp_c);
}
return 0;
}
int rhttp_main(int argc, char *argv[]) {
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
int opt;
while ((opt = getopt(argc, argv, "p:drh:bewi")) != -1) {
switch (opt) {
case 'i':
rhttp_opt_info = 1;
rhttp_opt_warn = 1;
rhttp_opt_error = 1;
break;
case 'e':
rhttp_opt_error = 1;
rhttp_opt_warn = 0;
rhttp_opt_info = 0;
break;
case 'w':
rhttp_opt_warn = 1;
rhttp_opt_error = 1;
rhttp_opt_info = 0;
break;
case 'p':
rhttp_opt_port = atoi(optarg);
break;
case 'b':
rhttp_opt_buffered = 1;
printf("Logging is buffered. Output may be incomplete.\n");
break;
case 'h':
strcpy(rhttp_opt_host, optarg);
break;
case 'd':
printf("Debug enabled\n");
rhttp_opt_debug = 1;
rhttp_opt_warn = 1;
rhttp_opt_info = 1;
rhttp_opt_error = 1;
break;
case 'r':
printf("Request logging enabled\n");
rhttp_opt_request_logging = 1;
break;
default:
printf("Usage: %s [-p port] [-h host] [-b]\n", argv[0]);
return 1;
}
}
printf("Starting server on: %s:%d\n", rhttp_opt_host, rhttp_opt_port);
if (rhttp_opt_buffered)
setvbuf(stdout, NULL, _IOFBF, BUFSIZ);
rhttp_serve(rhttp_opt_host, rhttp_opt_port, 1024, rhttp_opt_request_logging, rhttp_opt_debug, rhttp_default_request_handler, NULL);
return 0;
}
/* CLIENT CODE */
typedef struct rhttp_client_request_t {
char *host;
int port;
char *path;
bool is_done;
char *request;
char *response;
pthread_t thread;
int bytes_received;
} rhttp_client_request_t;
rhttp_client_request_t *rhttp_create_request(const char *host, int port, const char *path) {
rhttp_client_request_t *r = (rhttp_client_request_t *)malloc(sizeof(rhttp_client_request_t));
char request_line[4096] = {0};
sprintf(request_line,
"GET %s HTTP/1.1\r\n"
"Host: localhost:8000\r\n"
"Connection: close\r\n"
"Accept: */*\r\n"
"User-Agent: mhttpc\r\n"
"Accept-Language: en-US,en;q=0.5\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"\r\n",
path);
r->request = strdup(request_line);
r->host = strdup(host);
r->port = port;
r->path = strdup(path);
r->is_done = false;
r->response = NULL;
r->bytes_received = 0;
return r;
}
int rhttp_execute_request(rhttp_client_request_t *r) {
int s = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(r->port);
addr.sin_addr.s_addr = inet_addr(r->host);
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
return 0;
}
send(s, r->request, strlen(r->request), 0);
char buf[1024 * 1024] = {0};
int ret = recv(s, buf, 1024 * 1024, 0);
if (ret > 0) {
r->response = strdup(buf);
}
close(s);
return ret;
}
void rhttp_reset_request(rhttp_client_request_t *r) {
free(r->response);
r->is_done = false;
r->response = NULL;
r->bytes_received = 0;
}
void rhttp_free_client_request(rhttp_client_request_t *r) {
if (r->request)
free(r->request);
if (r->response)
free(r->response);
if (r->host)
free(r->host);
if (r->path)
free(r->path);
free(r);
}
void rhttp_client_bench(int workers, int times, const char *host, int port, const char *path) {
rhttp_client_request_t *requests[workers];
while (times > 0) {
for (int i = 0; i < workers && times; i++) {
requests[i] = rhttp_create_request(host, port, path);
rhttp_execute_request(requests[i]);
times--;
}
}
}
char *rhttp_client_get(const char *host, int port, const char *path) {
if (!rhttp_c_mutex_initialized) {
rhttp_c_mutex_initialized = 1;
pthread_mutex_init(&rhttp_c_mutex, NULL);
}
char http_response[1024 * 1024];
http_response[0] = 0;
rhttp_client_request_t *r = rhttp_create_request(host, port, path);
unsigned int reconnects = 0;
unsigned int reconnects_max = 100000;
while (!rhttp_execute_request(r)) {
reconnects++;
tick();
if (reconnects == reconnects_max) {
fprintf(stderr, "Maxium reconnects exceeded for %s:%d\n", host, port);
rhttp_free_client_request(r);
return NULL;
}
}
r->is_done = true;
char *body = r->response ? strstr(r->response, "\r\n\r\n") : NULL;
pthread_mutex_lock(&rhttp_c_mutex);
if (body) {
strcpy(http_response, body + 4);
} else {
strcpy(http_response, r->response);
}
rhttp_free_client_request(r);
char *result = sbuf(http_response);
pthread_mutex_unlock(&rhttp_c_mutex);
return result;
}
/*END CLIENT CODE */
#endif