big ol' update
This commit is contained in:
parent
bdafb30b0a
commit
f81c911898
44
Makefile
44
Makefile
@ -1,26 +1,22 @@
|
|||||||
all: server
|
# ----- Variables for customization -----
|
||||||
|
CC = gcc # Compiler (you can change to clang if desired)
|
||||||
|
CFLAGS = -g3 -Wall -Wextra -fsanitize=address,undefined # Standard flags + debugging & warnings
|
||||||
|
LDFLAGS = # Linker flags, if any
|
||||||
|
|
||||||
|
# ----- Automatic dependency tracking -----
|
||||||
|
SRCS := $(wildcard *.c) # Find all .c source files
|
||||||
|
OBJS := $(SRCS:.c=.o) # Corresponding object files (.o)
|
||||||
|
HDRS := $(SRCS:.c=.h)
|
||||||
|
|
||||||
|
# ----- Targets -----
|
||||||
|
server: $(OBJS)
|
||||||
|
$(CC) $(CFLAGS) $(OBJS) -o server $(LDFLAGS)
|
||||||
|
|
||||||
|
# Create object files from source files
|
||||||
|
%.o: %.c %.h
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
# Phony target for cleaning
|
||||||
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@rm -rf *.o
|
rm -f $(OBJS) server
|
||||||
@rm -rf server
|
|
||||||
|
|
||||||
server: main.o server.o request.o header.o utils.o response.o
|
|
||||||
gcc -g3 -fsanitize=address,undefined -o server $^
|
|
||||||
|
|
||||||
main.o: main.c
|
|
||||||
gcc -g3 -fsanitize=address,undefined -c -o main.o main.c
|
|
||||||
|
|
||||||
server.o: server.c server.h
|
|
||||||
gcc -g3 -fsanitize=address,undefined -c -o server.o server.c
|
|
||||||
|
|
||||||
request.o: request.c request.h
|
|
||||||
gcc -g3 -fsanitize=address,undefined -c -o request.o request.c
|
|
||||||
|
|
||||||
response.o: response.c response.h
|
|
||||||
gcc -g3 -fsanitize=address,undefined -c -o response.o response.c
|
|
||||||
|
|
||||||
header.o: header.c header.h
|
|
||||||
gcc -g3 -fsanitize=address,undefined -c -o header.o header.c
|
|
||||||
|
|
||||||
utils.o: utils.c utils.h
|
|
||||||
gcc -g3 -fsanitize=address,undefined -c -o utils.o utils.c
|
|
||||||
|
@ -1 +1 @@
|
|||||||
<p>It works!</p>
|
<p>see?</p>
|
||||||
|
5
header.c
5
header.c
@ -1,8 +1,9 @@
|
|||||||
#include "header.h"
|
#include "header.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
struct Header header_constructor(char *key, char *value) {
|
struct Header header_constructor(char *key, char *value) {
|
||||||
struct Header header;
|
struct Header header;
|
||||||
header.key = key;
|
header.key = strdup(key);
|
||||||
header.value = value;
|
header.value = strdup(value);
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
18
index.html
18
index.html
@ -1,7 +1,23 @@
|
|||||||
<div>
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Michael Thomson</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Michael Thomson</h1>
|
||||||
|
<p>Welcome to my site! doesn't it have so much content? I know, right?</p>
|
||||||
|
<p>It even has reactivity!</p>
|
||||||
|
<div>
|
||||||
<!-- have a button POST a click via AJAX -->
|
<!-- have a button POST a click via AJAX -->
|
||||||
<button hx-post="/clicked" hx-swap="outerHTML">
|
<button hx-post="/clicked" hx-swap="outerHTML">
|
||||||
Click Me
|
Click Me
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
11
main.c
11
main.c
@ -1,8 +1,19 @@
|
|||||||
|
#include "request.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
char *home(struct Request request) {
|
||||||
|
return read_file("index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *clicked(struct Request request) {
|
||||||
|
return read_file("clicked.html");
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
struct Server server = server_constructor("3000");
|
struct Server server = server_constructor("3000");
|
||||||
|
add_route(&server, "GET", "/", home);
|
||||||
|
add_route(&server, "POST", "/clicked", clicked);
|
||||||
launch(&server);
|
launch(&server);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
23
response.c
23
response.c
@ -1,23 +1,31 @@
|
|||||||
#include "response.h"
|
#include "response.h"
|
||||||
#include "header.h"
|
#include "header.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define MAX_BUFFER_SIZE 2048
|
#define MAX_BUFFER_SIZE 2048
|
||||||
|
|
||||||
struct Response response_constructor(char *version, char *status, char *body,
|
struct Response response_constructor(char *version, char *status, char *body) {
|
||||||
int num_headers, struct Header *headers) {
|
|
||||||
struct Response response;
|
struct Response response;
|
||||||
response.version = version;
|
response.version = strdup(version);
|
||||||
response.status = status;
|
response.status = strdup(status);
|
||||||
response.body = body;
|
response.body = strdup(body);
|
||||||
response.num_headers = num_headers;
|
response.num_headers = 0;
|
||||||
response.headers = headers;
|
response.headers = NULL;
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_header(struct Response *response, char *key, char *value) {
|
||||||
|
struct Header header = header_constructor(key, value);
|
||||||
|
response->headers = realloc(
|
||||||
|
response->headers, sizeof(struct Header) * (response->num_headers + 1));
|
||||||
|
response->headers[response->num_headers++] = header;
|
||||||
|
}
|
||||||
|
|
||||||
void response_to_string(char *buf, struct Response *response) {
|
void response_to_string(char *buf, struct Response *response) {
|
||||||
char headers[MAX_BUFFER_SIZE];
|
char headers[MAX_BUFFER_SIZE];
|
||||||
|
headers[0] = '\0';
|
||||||
|
|
||||||
for (int i = 0; i < response->num_headers; i++) {
|
for (int i = 0; i < response->num_headers; i++) {
|
||||||
strcat(headers, response->headers[i].key);
|
strcat(headers, response->headers[i].key);
|
||||||
@ -26,6 +34,7 @@ void response_to_string(char *buf, struct Response *response) {
|
|||||||
strcat(headers, "\r\n");
|
strcat(headers, "\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
snprintf(buf, MAX_BUFFER_SIZE, "%s %s\r\n%s\r\n%s", response->version,
|
snprintf(buf, MAX_BUFFER_SIZE, "%s %s\r\n%s\r\n%s", response->version,
|
||||||
response->status, headers, response->body);
|
response->status, headers, response->body);
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,10 @@ struct Response {
|
|||||||
struct Header *headers;
|
struct Header *headers;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Response response_constructor(char *version, char *status, char *body,
|
struct Response response_constructor(char *version, char *status, char *body);
|
||||||
int num_headers, struct Header *headers);
|
|
||||||
|
|
||||||
void response_to_string(char *buf, struct Response *response);
|
void response_to_string(char *buf, struct Response *response);
|
||||||
|
|
||||||
|
void add_header(struct Response *response, char *key, char *value);
|
||||||
|
|
||||||
#endif // !RESPONSE_H
|
#endif // !RESPONSE_H
|
||||||
|
12
route.c
Normal file
12
route.c
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "route.h"
|
||||||
|
|
||||||
|
struct Route route_constructor(char *target, char *method,
|
||||||
|
Route_Callback route_callback) {
|
||||||
|
struct Route route;
|
||||||
|
route.target = strdup(target);
|
||||||
|
route.method = strdup(method);
|
||||||
|
route.route_callback = route_callback;
|
||||||
|
return route;
|
||||||
|
}
|
17
route.h
Normal file
17
route.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef ROUTE_H
|
||||||
|
#define ROUTE_H
|
||||||
|
|
||||||
|
#include "request.h"
|
||||||
|
|
||||||
|
typedef char *(*Route_Callback)(struct Request request);
|
||||||
|
|
||||||
|
struct Route {
|
||||||
|
char *target;
|
||||||
|
char *method;
|
||||||
|
Route_Callback route_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Route route_constructor(char *target, char *method,
|
||||||
|
Route_Callback route_callback);
|
||||||
|
|
||||||
|
#endif // !ROUTE_H
|
76
server.c
76
server.c
@ -8,9 +8,9 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "header.h"
|
|
||||||
#include "request.h"
|
#include "request.h"
|
||||||
#include "response.h"
|
#include "response.h"
|
||||||
|
#include "route.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
#define MAX_BUFFER_SIZE 2048
|
#define MAX_BUFFER_SIZE 2048
|
||||||
@ -18,12 +18,32 @@
|
|||||||
|
|
||||||
int sockfd;
|
int sockfd;
|
||||||
|
|
||||||
struct Server server_constructor(char *PORT) {
|
struct Server server_constructor(const char *PORT) {
|
||||||
struct Server server;
|
struct Server server;
|
||||||
server.PORT = PORT;
|
server.PORT = PORT;
|
||||||
|
server.num_routes = 0;
|
||||||
|
server.routes = NULL;
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_route(struct Server *server, char *method, char *target,
|
||||||
|
Route_Callback route_callback) {
|
||||||
|
struct Route route = route_constructor(target, method, route_callback);
|
||||||
|
server->routes =
|
||||||
|
realloc(server->routes, sizeof(struct Route) * (server->num_routes + 1));
|
||||||
|
server->routes[server->num_routes++] = route;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Route *match_route(struct Server *server, char *method, char *target) {
|
||||||
|
for (int i = 0; i < server->num_routes; i++) {
|
||||||
|
if (strcmp(server->routes[i].target, target) == 0 &&
|
||||||
|
strcmp(server->routes[i].method, method) == 0) {
|
||||||
|
return &server->routes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void sigchld_handler(int s) {
|
void sigchld_handler(int s) {
|
||||||
// waitpid() might overwrite errno, so we save and restore it:
|
// waitpid() might overwrite errno, so we save and restore it:
|
||||||
int saved_errno = errno;
|
int saved_errno = errno;
|
||||||
@ -90,7 +110,7 @@ static void start(const char *PORT) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *read_file(char *filename, size_t *file_size) {
|
char *read_file(char *filename) {
|
||||||
// read in file
|
// read in file
|
||||||
FILE *fp = fopen(filename, "r");
|
FILE *fp = fopen(filename, "r");
|
||||||
if (fp == NULL) {
|
if (fp == NULL) {
|
||||||
@ -100,11 +120,11 @@ char *read_file(char *filename, size_t *file_size) {
|
|||||||
|
|
||||||
// Calculate the content length
|
// Calculate the content length
|
||||||
fseek(fp, 0, SEEK_END);
|
fseek(fp, 0, SEEK_END);
|
||||||
*file_size = ftell(fp);
|
size_t file_size = ftell(fp);
|
||||||
rewind(fp);
|
rewind(fp);
|
||||||
|
|
||||||
// allocate memory for file contents
|
// allocate memory for file contents
|
||||||
char *buffer = malloc(*file_size + 1);
|
char *buffer = malloc(file_size + 1);
|
||||||
if (buffer == NULL) {
|
if (buffer == NULL) {
|
||||||
fprintf(stderr, "read_file: memory allocation failed.\n");
|
fprintf(stderr, "read_file: memory allocation failed.\n");
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
@ -112,8 +132,8 @@ char *read_file(char *filename, size_t *file_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read the file contents into buffer
|
// Read the file contents into buffer
|
||||||
size_t bytes_read = fread(buffer, 1, *file_size, fp);
|
size_t bytes_read = fread(buffer, 1, file_size, fp);
|
||||||
if (bytes_read != *file_size) {
|
if (bytes_read != file_size) {
|
||||||
fprintf(stderr, "read_file: error reading file.\n");
|
fprintf(stderr, "read_file: error reading file.\n");
|
||||||
free(buffer);
|
free(buffer);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
@ -121,41 +141,37 @@ char *read_file(char *filename, size_t *file_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
buffer[*file_size] = '\0';
|
buffer[file_size] = '\0';
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void respond(int *client_fd) {
|
void respond(struct Server *server, int *client_fd) {
|
||||||
|
// Recieve request
|
||||||
char buf[MAX_BUFFER_SIZE];
|
char buf[MAX_BUFFER_SIZE];
|
||||||
recv(*client_fd, buf, MAX_BUFFER_SIZE, 0);
|
recv(*client_fd, buf, MAX_BUFFER_SIZE, 0);
|
||||||
|
|
||||||
|
// Parse request
|
||||||
struct Request request = request_constructor(buf);
|
struct Request request = request_constructor(buf);
|
||||||
|
|
||||||
printf("%s %s %s\n", request.method, request.target, request.version);
|
// Match route and get body content
|
||||||
|
struct Route *matched_route =
|
||||||
|
match_route(server, request.method, request.target);
|
||||||
|
|
||||||
char *filename;
|
struct Response response;
|
||||||
if (strcmp(request.target, "/") == 0) {
|
if (matched_route == NULL) {
|
||||||
filename = "index.html";
|
response = response_constructor("HTTP/1.1", NOT_FOUND, "Not found");
|
||||||
} else if (strcmp(request.target, "/clicked") == 0) {
|
|
||||||
filename = "clicked.html";
|
|
||||||
} else {
|
} else {
|
||||||
filename = "404.html";
|
char *body = matched_route->route_callback(request);
|
||||||
|
response = response_constructor("HTTP/1.1", OK, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t body_length;
|
// calculate content length
|
||||||
char *body = read_file(filename, &body_length);
|
char body_length[12];
|
||||||
|
snprintf(body_length, sizeof(body_length), "%lu", strlen(response.body));
|
||||||
|
add_header(&response, "Content-Length", body_length);
|
||||||
|
|
||||||
// Create the Content-Length header
|
printf("%s %s -> %s\n", request.method, request.target, response.status);
|
||||||
struct Header headers[MAX_HEADER_NUM];
|
|
||||||
char contentLength[MAX_BUFFER_SIZE];
|
|
||||||
snprintf(contentLength, sizeof(contentLength), "%zu", body_length);
|
|
||||||
headers[0] = header_constructor("Content-Length", contentLength);
|
|
||||||
|
|
||||||
// construct response
|
|
||||||
struct Response response =
|
|
||||||
response_constructor("HTTP/1.1", OK, body, 1, headers);
|
|
||||||
|
|
||||||
// send response string
|
// send response string
|
||||||
char response_string[MAX_BUFFER_SIZE];
|
char response_string[MAX_BUFFER_SIZE];
|
||||||
@ -165,8 +181,6 @@ void respond(int *client_fd) {
|
|||||||
perror("send content");
|
perror("send content");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void launch(struct Server *server) {
|
void launch(struct Server *server) {
|
||||||
@ -185,7 +199,7 @@ void launch(struct Server *server) {
|
|||||||
|
|
||||||
if (!fork()) {
|
if (!fork()) {
|
||||||
close(sockfd);
|
close(sockfd);
|
||||||
respond(&client_fd);
|
respond(server, &client_fd);
|
||||||
close(client_fd);
|
close(client_fd);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
10
server.h
10
server.h
@ -2,15 +2,23 @@
|
|||||||
#define SERVER_H
|
#define SERVER_H
|
||||||
|
|
||||||
#include "header.h"
|
#include "header.h"
|
||||||
|
#include "route.h"
|
||||||
|
|
||||||
#define BACKLOG 128
|
#define BACKLOG 128
|
||||||
|
|
||||||
struct Server {
|
struct Server {
|
||||||
const char *PORT;
|
const char *PORT;
|
||||||
|
struct Route *routes;
|
||||||
|
int num_routes;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Server server_constructor(char *PORT);
|
struct Server server_constructor(const char *PORT);
|
||||||
|
|
||||||
|
void add_route(struct Server *server, char *method, char *target,
|
||||||
|
Route_Callback route_callback);
|
||||||
|
|
||||||
void launch(struct Server *server);
|
void launch(struct Server *server);
|
||||||
|
|
||||||
|
char *read_file(char *filename);
|
||||||
|
|
||||||
#endif // !SERVER_H
|
#endif // !SERVER_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user