backend: fixies

This commit is contained in:
Simon 2023-02-13 01:39:21 +01:00
parent 28a91d9f6b
commit 83f95f86b9
4 changed files with 363 additions and 261 deletions

View File

@ -1,9 +1,11 @@
#include "tcp.h" #include "tcp.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
@ -36,10 +38,12 @@ TcpServer* tcp_server_create(const char* ip, uint16_t port)
server_address.sin_port = htons(port); server_address.sin_port = htons(port);
if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
printf("error: tcp: could not bind socket\n"); printf("error: tcp: could not bind socket\n");
close(server_socket);
return NULL; return NULL;
} }
if (listen(server_socket, SOMAXCONN) < 0) { if (listen(server_socket, SOMAXCONN - 1) < 0) {
printf("error: tcp: could not listen on server\n"); printf("error: tcp: could not listen on server\n");
close(server_socket);
return NULL; return NULL;
} }
@ -59,12 +63,13 @@ void tcp_server_destroy(TcpServer* server)
TcpConnection* tcp_server_accept(TcpServer* server) TcpConnection* tcp_server_accept(TcpServer* server)
{ {
struct sockaddr_in client_address; struct sockaddr_in client_address = { 0 };
socklen_t client_size; socklen_t client_size = 0;
int client_socket int client_socket
= accept(server->server_socket, (struct sockaddr*)&client_address, &client_size); = accept(server->server_socket, (struct sockaddr*)&client_address, &client_size);
if (client_socket < 0) { if (client_socket < 0) {
printf("error: tcp: could not accept connection\n"); printf("error: tcp: could not accept connection\n");
printf("errno: %d %s\n", errno, strerror(errno));
return NULL; return NULL;
} }

View File

@ -7,8 +7,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
const uint16_t port = 8000;
TcpServer* server = NULL; TcpServer* server = NULL;
void interrupt_handler(int a) void interrupt_handler(int a)
@ -20,38 +18,19 @@ void interrupt_handler(int a)
exit(1); exit(1);
} }
int main(void) void handle_client_connection(TcpConnection* connection)
{ {
tcp_global_initialize_sockets();
signal(SIGINT, &interrupt_handler);
printf("starting server...\n");
server = tcp_server_create("127.0.0.1", port);
if (server == NULL)
return 1;
printf("listening on port %d\n", port);
while (true) {
printf("waiting for client...\n");
TcpConnection* client_connection = tcp_server_accept(server);
if (client_connection == NULL) {
printf("error: could not accept client\n");
continue;
}
printf("client connected\n");
while (true) {
uint8_t buffer[8192] = { 0 }; uint8_t buffer[8192] = { 0 };
ssize_t recieved = tcp_recieve(client_connection, buffer, 8192); ssize_t recieved = tcp_recieve(connection, buffer, 8192);
if (recieved < 0) { if (recieved < 0) {
printf("error: could not recieve\n"); printf("error: could not recieve\n");
break; return;
} else if (recieved == 0) { } else if (recieved == 0) {
printf("client disconnected\n"); printf("client disconnected\n");
break; return;
} }
HttpRequestHeader header HttpRequestHeader header = parse_http_request_header((char*)buffer, strlen((char*)buffer));
= parse_http_request_header((char*)buffer, strlen((char*)buffer));
char* path = calloc(header.path_length + 1, sizeof(char)); char* path = calloc(header.path_length + 1, sizeof(char));
strncpy(path, (char*)&buffer[header.path_index], header.path_length); strncpy(path, (char*)&buffer[header.path_index], header.path_length);
if (strncmp(path, "/api", 4) == 0) { if (strncmp(path, "/api", 4) == 0) {
@ -64,28 +43,28 @@ int main(void)
"\r\n" "\r\n"
"<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Bad " "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Bad "
"request</title></head><body><h1>Fuck you!</h1></body></html>\r\n"; "request</title></head><body><h1>Fuck you!</h1></body></html>\r\n";
ssize_t written = tcp_send(client_connection, send_buffer, sizeof(send_buffer)); ssize_t written = tcp_send(connection, send_buffer, sizeof(send_buffer));
if (written < 0) { if (written < 0) {
printf("error: could not write\n"); printf("error: could not write\n");
break; return;
} }
} else if (header.path_length == 0 || strncmp(path, "/", header.path_length) == 0) { } else if (header.path_length == 0 || strncmp(path, "/", header.path_length) == 0) {
FILE* file = fopen("../frontend/index.html", "r"); FILE* file = fopen("../frontend/index.html", "r");
if (file == NULL) { if (file == NULL) {
printf("error: could not open file\n"); printf("error: could not open file\n");
break; return;
} }
uint8_t send_buffer[] = "HTTP/1.1 200 OK\r\n" uint8_t send_buffer[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n" "Content-Type: text/html\r\n"
"\r\n"; "\r\n";
ssize_t written = tcp_send(client_connection, send_buffer, sizeof(send_buffer)); ssize_t written = tcp_send(connection, send_buffer, sizeof(send_buffer));
if (written < 0) { if (written < 0) {
printf("error: could not write\n"); printf("error: could not write\n");
break; return;
} }
char char_read; char char_read;
while ((char_read = (char)fgetc(file)) != EOF) { while ((char_read = (char)fgetc(file)) != EOF) {
tcp_send(client_connection, (uint8_t*)&char_read, sizeof(char)); tcp_send(connection, (uint8_t*)&char_read, sizeof(char));
} }
} else { } else {
char rootpath[] = "../frontend"; char rootpath[] = "../frontend";
@ -117,39 +96,37 @@ int main(void)
file_flag[1] = 'b'; file_flag[1] = 'b';
} else { } else {
printf("error: unknown file type\n"); printf("error: unknown file type\n");
break; return;
} }
FILE* file = fopen(filepath, file_flag); FILE* file = fopen(filepath, file_flag);
if (file == NULL) { if (file == NULL) {
printf("error: could not open file\n"); printf("error: could not open file\n");
break; return;
} }
char send_buffer_1[] = "HTTP/1.1 200 OK\r\n" char send_buffer_1[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: "; "Content-Type: ";
char send_buffer_2[] = "\r\n" char send_buffer_2[] = "\r\n"
"\r\n"; "\r\n";
ssize_t written = tcp_send( ssize_t written = tcp_send(connection, (uint8_t*)send_buffer_1, strlen(send_buffer_1));
client_connection, (uint8_t*)send_buffer_1, strlen(send_buffer_1));
if (written < 0) { if (written < 0) {
printf("error: could not write\n"); printf("error: could not write\n");
break; return;
} }
written = tcp_send(client_connection, (uint8_t*)mime_type, strlen(mime_type)); written = tcp_send(connection, (uint8_t*)mime_type, strlen(mime_type));
if (written < 0) { if (written < 0) {
printf("error: could not write\n"); printf("error: could not write\n");
break; return;
} }
written = tcp_send( written = tcp_send(connection, (uint8_t*)send_buffer_2, strlen(send_buffer_2));
client_connection, (uint8_t*)send_buffer_2, strlen(send_buffer_2));
if (written < 0) { if (written < 0) {
printf("error: could not write\n"); printf("error: could not write\n");
break; return;
} }
int char_read; int char_read;
while ((char_read = fgetc(file)) != EOF) { while ((char_read = fgetc(file)) != EOF) {
tcp_send(client_connection, (uint8_t*)&char_read, sizeof(char)); tcp_send(connection, (uint8_t*)&char_read, sizeof(char));
} }
free(filepath); free(filepath);
@ -157,10 +134,33 @@ int main(void)
} }
} }
free(path); free(path);
break;
} }
int main(void)
{
tcp_global_initialize_sockets();
signal(SIGINT, &interrupt_handler);
const uint16_t port = 8000;
printf("starting server...\n");
server = tcp_server_create("127.0.0.1", port);
if (server == NULL)
return 1;
printf("listening on port %d\n", port);
while (true) {
printf("waiting for client...\n");
TcpConnection* connection = tcp_server_accept(server);
if (connection == NULL) {
printf("error: could not accept client\n");
continue;
}
printf("client connected\n");
handle_client_connection(connection);
printf("disconnecting client\n"); printf("disconnecting client\n");
tcp_connection_destroy(client_connection); tcp_connection_destroy(connection);
} }
tcp_server_destroy(server); tcp_server_destroy(server);
return 0; return 0;

View File

@ -15,29 +15,5 @@
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
</head> </head>
<body> <body>
<div id="topbar">
<h1>Postnummer App</h1>
<div class="spacer"></div>
<button id="dropdown-button"></button>
<div id="dropdown">
<button id="map-redirect">Kort</button>
<button id="reviews-redirect">Anmeldelser</button>
</div>
</div>
<main id="main">
<form id="search-bar">
<input id="search-input" type="text" placeholder="Postnummer" maxlength="4">
<button id="search-button" type="submit">Search</button>
</form>
<img src="assets/map.jpg" id="map">
<div id="dot"></div>
<div id="boundary"></div>
<div id="info">
<p id="zip-code">Postnummer ikke fundet</p>
<p id="mouse-position"></p>
<p id="coords"></p>
</div>
</main>
<div id="tooltip"></div>
</body> </body>
</html> </html>

View File

@ -120,11 +120,136 @@ function displayZipCode(
boundaryElem.style.height = `${bottomleft.y - topright.y}px`; boundaryElem.style.height = `${bottomleft.y - topright.y}px`;
} }
function setupMap( type Component = {
mousePositionElement: HTMLParagraphElement, children?: Component[],
coordsElement: HTMLParagraphElement, render(): string,
zipCodeElement: HTMLParagraphElement, hydrate?(update: (action?: () => Promise<any>) => Promise<void>): Promise<void>,
) { };
function makeReviewsPage(): Component {
return {
render: () => /*html*/`
<h2 id="reviews-title">Anmeldelser</h2>
<div id="reviews-container">${loadReviews()}</div>
`,
}
}
function makeSearchBar(updateZipCodeInfo: (zipCode: string) => Promise<void>): Component {
return {
render: () => /*html*/`
<form id="search-bar">
<input id="search-input" type="text" placeholder="Postnummer" maxlength="4">
<button id="search-button" type="submit">Search</button>
</form>
`,
async hydrate(update) {
const searchBar =
domSelect<HTMLFormElement>("#search-bar")!;
const searchInput =
domSelect<HTMLInputElement>("#search-input")!;
searchInput.addEventListener("keydown", (event) => {
if (event.key.length === 1 && !"0123456789".includes(event.key))
event.preventDefault();
});
searchBar.addEventListener("submit", async (event: Event) => {
event.preventDefault();
update(() => updateZipCodeInfo(searchInput.value));
});
},
};
}
type ZipCodeInfo = {
zipCode: number | null,
name: string | null,
center: Coordinate | null,
boundary: Square | null,
}
function makeInfoBox(info: ZipCodeInfo): Component {
const { zipCode, name, center, boundary } = info;
return {
render: () => /*html*/`
<div id="info">
<p id="zip-code">${info.zipCode === null ? "Postnummer ikke fundet" : `Postnummer: <code>${zipCode}</code>, ${name}`}</p>
<p id="mouse-position"></p>
<p id="coords"></p>
</div>
`,
async hydrate() {
tooltip.setText(zipCode ? `<code>${zipCode}</code> ${name}` : "");
const dot = document.getElementById("dot")!;
const boundaryElem = document.getElementById("boundary")!;
if (!center || !boundary) {
dot.style.display = "none";
boundaryElem.style.display = "none";
return;
}
const mapImg = document.getElementById("map")!;
const mapSize: Size = {
width: mapImg.clientWidth,
height: mapImg.clientHeight,
};
// Draw dot
const position = convertCoordinateToPixels(center, mapSize);
const rect = document.getElementById("map")!.getBoundingClientRect();
dot.style.display = "block";
dot.style.left = `${position.x + rect.left}px`;
dot.style.top = `${position.y + rect.top + document.documentElement.scrollTop}px`;
// Draw boundary
const bottomleft = convertCoordinateToPixels({ longitude: boundary.x1, latitude: boundary.y1 }, mapSize);
const topright = convertCoordinateToPixels({ longitude: boundary.x2, latitude: boundary.y2 }, mapSize);
boundaryElem.style.display = "block";
boundaryElem.style.left = `${bottomleft.x + rect.left}px`;
boundaryElem.style.top = `${topright.y + rect.top + document.documentElement.scrollTop}px`;
boundaryElem.style.width = `${topright.x - bottomleft.x}px`;
boundaryElem.style.height = `${bottomleft.y - topright.y}px`;
}
}
}
function makeMainPage(): Component {
const zipCodeInfo: ZipCodeInfo = {
zipCode: null,
name: null,
center: null,
boundary: null,
}
const searchBar = makeSearchBar(async (searchInputValue) => {
const inputValue = searchInputValue;
if (!/^\d+$/.test(inputValue)) return;
const data = await fetch(
`https://api.dataforsyningen.dk/postnumre?nr=${inputValue}`,
).then((response) => response.json())
zipCodeInfo.zipCode = data.length ? parseInt(data[0]["nr"]) : null;
zipCodeInfo.name = data.length ? data[0]["navn"] : null;
zipCodeInfo.center = data.length ? { longitude: data[0]["visueltcenter"][0], latitude: data[0]["visueltcenter"][1] } : null;
zipCodeInfo.boundary = data.length ? { x1: data[0]["bbox"][0], y1: data[0]["bbox"][1], x2: data[0]["bbox"][2], y2: data[0]["bbox"][3] } : null;
});
const infoBox = makeInfoBox(zipCodeInfo);
return {
children: [searchBar, infoBox],
render: () => /*html*/`
${searchBar.render()}
<img src="assets/map.jpg" id="map">
<div id="dot"></div>
<div id="boundary"></div>
`,
async hydrate() {
const mousePositionElement = domSelect<HTMLParagraphElement>("#mouse-position")!;
const coordsElement = domSelect<HTMLParagraphElement>("#coords")!;
const zipCodeElement = domSelect<HTMLParagraphElement>("#zip-code")!;
const mapImg = domSelect<HTMLImageElement>("#map")!; const mapImg = domSelect<HTMLImageElement>("#map")!;
const fetcher = new Throttler(200); const fetcher = new Throttler(200);
@ -161,97 +286,93 @@ function setupMap(
mapImg.addEventListener("mouseleave", (_event: MouseEvent) => { mapImg.addEventListener("mouseleave", (_event: MouseEvent) => {
displayZipCode(zipCodeElement, null, null, null, null); displayZipCode(zipCodeElement, null, null, null, null);
}); });
},
};
} }
function setupSearchBar(zipCodeElement: HTMLParagraphElement) { function makeRouter(route: "main" | "reviews"): Component {
const searchBar = switch (route) {
domSelect<HTMLFormElement>("#search-bar")!; case "main":
const searchInput = {
domSelect<HTMLInputElement>("#search-input")!; const mainPage = makeMainPage();
return {
searchInput.addEventListener("keydown", (event) => { children: [mainPage],
if (event.key.length === 1 && !"0123456789".includes(event.key)) render: () => mainPage.render(),
event.preventDefault(); };
}); }
case "reviews": {
searchBar.addEventListener("submit", async (event: Event) => { const reviewsPage = makeReviewsPage();
event.preventDefault(); return {
children: [reviewsPage],
const inputValue = searchInput.value; render: () => reviewsPage.render(),
if (!/^\d+$/.test(inputValue)) return; }
const data = await fetch( }
`https://api.dataforsyningen.dk/postnumre?nr=${inputValue}`, }
).then((response) => response.json())
displayZipCode(
zipCodeElement,
data.length ? parseInt(data[0]["nr"]) : null,
data.length ? data[0]["navn"] : null,
data.length ? { longitude: data[0]["visueltcenter"][0], latitude: data[0]["visueltcenter"][1] } : null,
data.length ? { x1: data[0]["bbox"][0], y1: data[0]["bbox"][1], x2: data[0]["bbox"][2], y2: data[0]["bbox"][3] } : null,
);
});
} }
function pageRedirects() { function makeTooltip(): Component {
const reviewRedirect = document.getElementById("reviews-redirect")! return {
const mapRedirect = document.getElementById("map-redirect")! render: () => /*html*/`
const mainElement = document.getElementById("main")! <div id="tooltip"></div>
`,
}
}
function makeLayout(): Component {
reviewRedirect.addEventListener("click", () => { let route: "main" | "reviews" = "main";
mainElement.innerHTML = /*html*/` const router = makeRouter(route);
<h2 id="reviews-title">Anmeldelser</h2> const tooltip = makeTooltip();
<div id="reviews-container">${loadReviews()}</div> return {
`; children: [router, tooltip],
const dropdown = document.getElementById("dropdown")!; render: () => /**/`
dropdown.classList.remove("enabled"); <div id="topbar">
<h1>Postnummer App</h1>
}); <div class="spacer"></div>
<button id="dropdown-button"></button>
mapRedirect.addEventListener("click", () => { <div id="dropdown">
mainElement.innerHTML = /*html*/` <button id="map-redirect">Kort</button>
<form id="search-bar"> <button id="reviews-redirect">Anmeldelser</button>
<input id="search-input" type="text" placeholder="Postnummer" maxlength="4">
<button id="search-button" type="submit">Search</button>
</form>
<img src="assets/map.jpg" id="map">
<div id="dot"></div>
<div id="boundary"></div>
<div id="info">
<p id="zip-code">Postnummer ikke fundet</p>
<p id="mouse-position"></p>
<p id="coords"></p>
</div> </div>
`; </div>
const [mousePositionElement, coordsElement, zipCodeElement] = [ <main id="main">
"#mouse-position", ${router.render()}
"#coords", </main>
"#zip-code", ${tooltip.render()}
].map((id) => domSelect<HTMLParagraphElement>(id)!); `,
setupSearchBar(zipCodeElement); async hydrate(update) {
setupMap(mousePositionElement, coordsElement, zipCodeElement); setTopbarOffset();
const dropdown = document.getElementById("dropdown")!; addToggleDropdownListener();
dropdown.classList.remove("enabled"); document.getElementById("reviews-redirect")!.addEventListener('click', () => {
update(async () => route = "reviews");
}) })
document.getElementById("map-redirect")!.addEventListener('click', () => {
update(async () => route = "main");
})
},
}
}
function hydrateComponentAndChildren(component: Component, update: (action?: () => Promise<any>) => Promise<void>) {
component.hydrate && component.hydrate(update);
component.children?.forEach((child) => hydrateComponentAndChildren(child, update));
}
function renderComponent(element: HTMLElement, component: Component) {
element.innerHTML = component.render();
window.requestAnimationFrame(() => {
hydrateComponentAndChildren(component, async (action) => {
action && await action();
element.innerHTML = component.render();
});
});
} }
function main() { function main() {
if (navigator.userAgent.match("Chrome")) { if (navigator.userAgent.match("Chrome")) {
location.href = "https://mozilla.org/firefox"; location.href = "https://mozilla.org/firefox";
} }
const bodyElement = document.querySelector<HTMLElement>("body")!;
const mousePositionElement = domSelect<HTMLParagraphElement>("#mouse-position")!; renderComponent(bodyElement, makeLayout());
const coordsElement = domSelect<HTMLParagraphElement>("#coords")!;
const zipCodeElement = domSelect<HTMLParagraphElement>("#zip-code")!;
setupSearchBar(zipCodeElement);
setupMap(mousePositionElement, coordsElement, zipCodeElement);
setTopbarOffset();
addToggleDropdownListener();
pageRedirects();
} }
main(); main();