Skip to content

Commit db3b09a

Browse files
committed
initial
1 parent 5beb19c commit db3b09a

File tree

7 files changed

+409
-0
lines changed

7 files changed

+409
-0
lines changed

Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM alpine:latest AS build
2+
3+
WORKDIR /app
4+
5+
RUN apk add --no-cache \
6+
clang \
7+
llvm \
8+
musl-dev \
9+
build-base
10+
11+
COPY . .
12+
13+
# Compile - # clang main.c -o app -static
14+
RUN make all
15+
16+
# Etapa final: contêiner mínimo para executar o binário
17+
FROM alpine:latest
18+
19+
WORKDIR /home
20+
21+
COPY --from=build /app/simple-server .
22+
COPY --from=build /app/epoll-server .
23+
24+
#CMD ["./app"]

Makefile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
CC = clang
2+
CFLAGS = -static -lm -Wall -Wextra -O2
3+
4+
# files
5+
SOURCES = simple-server.c epoll-server.c #uring-server.c
6+
TARGETS = simple epoll #uring-server
7+
8+
all: $(TARGETS)
9+
10+
# Regras específicas para cada arquivo
11+
simple: simple-server.c
12+
$(CC) -o simple-server simple-server.c request-handle.c cpu-bound.c $(CFLAGS)
13+
# $(CC) $(CFLAGS) -o $@ $< -DFILE1_DEFINE
14+
15+
epoll: epoll-server.c
16+
$(CC) -o epoll-server epoll-server.c request-handle.c cpu-bound.c $(CFLAGS)
17+
# $(CC) $(CFLAGS) -o $@ $< -DFILE2_DEFINE -DADDITIONAL_FLAG
18+
19+
uring: uring-server.c
20+
# # $(CC) $(CFLAGS) -o $@ $< -DFILE3_DEFINE -lm
21+
$(CC) -g -o uring-server uring-server.c request-handle.c cpu-bound.c $(CFLAGS) -I/usr/local/include -L/usr/local/lib -luring
22+
# # musl-gcc -o rest_server rest_server.c -I/usr/local/include -L/usr/local/lib -luring
23+
24+
25+
clean:
26+
rm -f $(TARGETS)

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# c-http-server-from-scratch
2+
3+
This is a implementation of Http server using directly Linux SystemCalls.
4+
5+
There are some implementations:
6+
- simple-server.c: Using only accept syscall.
7+
- epoll-server.c: Using epoll syscall with 10 events.
8+
- io_uring.c: TODO.
9+
10+
### Default port is 8080.
11+
12+
# Endpoints:
13+
- GET /health-check: just return Healthy!
14+
- GET /api/io-bound: simulate io-bound with sleep
15+
- GET /api/cpu-bound: simulate cpu-bound with prime numbers
16+
17+
# Params QueryString
18+
- timeout: in ms (default is 10ms)
19+
20+
# Entrypoints:
21+
- /home/simple-server
22+
- /home/epoll-server

epoll-server.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#include <sys/epoll.h>
2+
#include <sys/socket.h>
3+
#include <netinet/in.h>
4+
#include <unistd.h>
5+
#include <fcntl.h>
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
#include <signal.h>
10+
#include <unistd.h>
11+
#include "request-handle.h"
12+
13+
#define PORT 8080
14+
#define MAX_EVENTS 10
15+
#define BUFFER_SIZE 1024
16+
17+
int server_fd = -1; // global server socket
18+
19+
void cleanup_and_exit(int signum) {
20+
if (server_fd != -1) {
21+
printf("\nRecebido sinal %d, fechando o servidor...\n", signum);
22+
close(server_fd);
23+
}
24+
exit(0);
25+
}
26+
27+
void setup_signal_handler() {
28+
struct sigaction sa;
29+
memset(&sa, 0, sizeof(sa));
30+
sa.sa_handler = cleanup_and_exit;
31+
sa.sa_flags = 0;
32+
33+
// Mascara de sinais vazia
34+
sigemptyset(&sa.sa_mask);
35+
36+
// Configurar para SIGINT (Ctrl+C) e SIGTERM (kill)
37+
if (sigaction(SIGINT, &sa, NULL) == -1) {
38+
perror("Erro ao configurar SIGINT");
39+
exit(EXIT_FAILURE);
40+
}
41+
if (sigaction(SIGTERM, &sa, NULL) == -1) {
42+
perror("Erro ao configurar SIGTERM");
43+
exit(EXIT_FAILURE);
44+
}
45+
}
46+
47+
void set_nonblocking(int sock) {
48+
int flags = fcntl(sock, F_GETFL, 0);
49+
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
50+
}
51+
52+
int main() {
53+
int epoll_fd;
54+
struct sockaddr_in server_addr;
55+
struct epoll_event event, events[MAX_EVENTS];
56+
57+
setup_signal_handler();
58+
59+
// Criar socket
60+
server_fd = socket(AF_INET, SOCK_STREAM, 0);
61+
if (server_fd < 0) {
62+
perror("Erro ao criar socket");
63+
exit(EXIT_FAILURE);
64+
}
65+
66+
// Configurar endereço e bind
67+
server_addr.sin_family = AF_INET;
68+
server_addr.sin_port = htons(PORT);
69+
server_addr.sin_addr.s_addr = INADDR_ANY;
70+
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
71+
perror("Erro ao associar socket");
72+
close(server_fd);
73+
exit(EXIT_FAILURE);
74+
}
75+
76+
// Colocar o socket em escuta
77+
if (listen(server_fd, MAX_EVENTS) < 0) {
78+
perror("Erro ao escutar socket");
79+
close(server_fd);
80+
exit(EXIT_FAILURE);
81+
}
82+
83+
// Criar epoll
84+
epoll_fd = epoll_create1(0);
85+
if (epoll_fd == -1) {
86+
perror("Erro ao criar epoll");
87+
close(server_fd);
88+
exit(EXIT_FAILURE);
89+
}
90+
91+
// Adicionar o socket do servidor ao epoll
92+
event.events = EPOLLIN; // Monitorar para leitura
93+
event.data.fd = server_fd;
94+
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
95+
perror("Erro ao adicionar ao epoll");
96+
close(server_fd);
97+
close(epoll_fd);
98+
exit(EXIT_FAILURE);
99+
}
100+
101+
printf("Servidor ouvindo na porta %d\n", PORT);
102+
103+
while (1) {
104+
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
105+
for (int i = 0; i < n; i++) {
106+
if (events[i].data.fd == server_fd) {
107+
// Aceitar nova conexão
108+
int client_fd = accept(server_fd, NULL, NULL);
109+
set_nonblocking(client_fd);
110+
event.events = EPOLLIN | EPOLLET;
111+
event.data.fd = client_fd;
112+
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
113+
printf("Nova conexão aceita\n");
114+
} else {
115+
// process request
116+
handle_request(events[i].data.fd);
117+
}
118+
}
119+
}
120+
121+
close(server_fd);
122+
return 0;
123+
}

request-handle.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <unistd.h>
5+
#include <sys/socket.h>
6+
#include <netinet/in.h>
7+
#include <arpa/inet.h>
8+
#include <signal.h>
9+
#include <string.h>
10+
#include <unistd.h>
11+
#include "cpu-bound.h"
12+
#include "request-handle.h"
13+
14+
#define BUF_SIZE 1024
15+
16+
17+
char* getParamQueryString(char buffer[BUF_SIZE], const char *param_name) {
18+
char *query_string = strstr(buffer, "?");
19+
20+
if (query_string != NULL) {
21+
query_string++; // Move o ponteiro para depois do '?'
22+
char *param_value = NULL;
23+
24+
size_t param_name_length = strlen(param_name);
25+
char *param_start = strstr(query_string, param_name);
26+
27+
if (param_start != NULL && *(param_start + param_name_length) == '=') {
28+
param_start += param_name_length + 1; // Move o ponteiro para depois de 'param_name='
29+
char *end_of_param = strchr(param_start, '&'); // Encontra o próximo '&' ou fim da string
30+
char *end_of_param_fallback = strchr(param_start, ' '); // Encontra o próximo ' ' ou fim da string
31+
32+
if (end_of_param != NULL) {
33+
size_t param_length = end_of_param - param_start; // Calcula o tamanho do valor do parâmetro
34+
param_value = malloc(param_length + 1); // Aloca memória para o valor do parâmetro
35+
strncpy(param_value, param_start, param_length);
36+
param_value[param_length] = '\0'; // Adiciona o terminador nulo
37+
} else if (end_of_param_fallback != NULL) {
38+
size_t param_length = end_of_param_fallback - param_start; // Calcula o tamanho do valor do parâmetro
39+
param_value = malloc(param_length + 1); // Aloca memória para o valor do parâmetro
40+
strncpy(param_value, param_start, param_length);
41+
param_value[param_length] = '\0';
42+
} else {
43+
// Se não houver '&', pega até o final da string
44+
param_value = strdup(param_start); // Duplica a string até o final
45+
}
46+
}
47+
48+
return param_value; // Retorna o valor do parâmetro ou NULL se não encontrado
49+
}
50+
51+
return NULL;
52+
}
53+
54+
void handle_request(int client_fd) {
55+
char buffer[BUF_SIZE];
56+
int bytes_read = read(client_fd, buffer, BUF_SIZE - 1);
57+
58+
if (bytes_read < 0) {
59+
perror("Erro ao ler do cliente");
60+
close(client_fd);
61+
return;
62+
}
63+
64+
buffer[bytes_read] = '\0'; // Garante que o buffer seja uma string
65+
printf("Requisição recebida:\n%s\n", buffer);
66+
67+
//params
68+
char *timeout = getParamQueryString(buffer, "timeout");
69+
if (timeout == NULL) {
70+
timeout = "10"; // ms
71+
}
72+
73+
const char *response =
74+
"HTTP/1.1 200 OK\r\n"
75+
"Content-Type: application/json\r\n"
76+
"Content-Length: 32\r\n"
77+
"\r\n"
78+
"{\"message\": \"request completed\"}";
79+
80+
// Routing
81+
if (strstr(buffer, "GET /api/cpu-bound") == buffer) {
82+
83+
simulateCPU(atoi(timeout));
84+
85+
write(client_fd, response, strlen(response));
86+
87+
} else if (strstr(buffer, "GET /api/io-bound") == buffer) {
88+
89+
usleep(atoi(timeout) * 1000);
90+
91+
write(client_fd, response, strlen(response));
92+
93+
} else if (strstr(buffer, "GET /health") == buffer) {
94+
95+
// response 200
96+
char *resp = "HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nHealthy!";
97+
write(client_fd, resp, strlen(resp));
98+
99+
} else {
100+
101+
// response 404
102+
char *resp = "HTTP/1.1 404 Not Found\r\nContent-Length: 16\r\n\r\nRoute not found!";
103+
write(client_fd, resp, strlen(resp));
104+
}
105+
106+
//free(timeout);
107+
//free(resp_size);
108+
close(client_fd);
109+
}

request-handle.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#ifndef REQUEST_HANDLE_H
2+
#define REQUEST_HANDLE_H
3+
4+
5+
void handle_request(int);
6+
7+
#endif

0 commit comments

Comments
 (0)