initial http server
This commit is contained in:
commit
172934e8a0
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
first
|
||||
.build
|
||||
133
main.jai
Normal file
133
main.jai
Normal file
@ -0,0 +1,133 @@
|
||||
#import "Basic";
|
||||
#import "Hash_Table";
|
||||
#import "Socket";
|
||||
String :: #import "String";
|
||||
|
||||
#load "server.jai";
|
||||
|
||||
consoom :: (input: *string, delimiter: string) -> string {
|
||||
if input.count < 1 then return "INVALID READ";
|
||||
found, left, right := String.split_from_left(input.*, delimiter);
|
||||
input.* = right;
|
||||
return left;
|
||||
}
|
||||
|
||||
Request :: struct {
|
||||
route : string;
|
||||
method : string;
|
||||
protocol : string;
|
||||
headers : Table(string, string);
|
||||
body : string;
|
||||
}
|
||||
|
||||
Response :: struct {
|
||||
protocol : string = "HTTP/1.1";
|
||||
status : int = 200;
|
||||
headers : Table(string, string);
|
||||
body : string;
|
||||
}
|
||||
|
||||
parse_request :: (data: *string) -> Request {
|
||||
req := New(Request);
|
||||
req.method = consoom(data, " ");
|
||||
req.route = consoom(data, " ");
|
||||
req.protocol = consoom(data, "\n");
|
||||
headers_string := consoom(data, "\r\n\r\n");
|
||||
|
||||
while headers_string.count > 0 {
|
||||
key := consoom(*headers_string, ": ");
|
||||
value := consoom(*headers_string, "\n");
|
||||
table_add(*req.headers, key, value);
|
||||
}
|
||||
|
||||
req.body = data.*;
|
||||
return req;
|
||||
}
|
||||
|
||||
set_header :: (res: *Response, key: string, value: string) {
|
||||
table_add(*res.headers, key, value);
|
||||
}
|
||||
|
||||
serialize_response :: (res: *Response) -> string {
|
||||
builder : String_Builder;
|
||||
print_to_builder(*builder, "% % OK", res.protocol, res.status);
|
||||
for v, k : res.headers {
|
||||
print("%: %\n", k,v);
|
||||
print_to_builder(*builder, "\n%: %", k, v);
|
||||
}
|
||||
print_to_builder(*builder, "\r\n\r\n");
|
||||
print_to_builder(*builder, "%", res.body);
|
||||
return builder_to_string(*builder);
|
||||
}
|
||||
|
||||
sigaction_t :: struct {
|
||||
sa_sigaction: (sig: s32, info: *siginfo_t, p: *void) #c_call;
|
||||
sa_mask: sigset_t; // Signal mask to apply.
|
||||
sa_flags: s32; // See signal options below.
|
||||
|
||||
#if OS == .LINUX then sa_restorer: () #c_call;
|
||||
}
|
||||
|
||||
libc :: #library,system "libc";
|
||||
|
||||
|
||||
sigaction :: (signum: s32, act: *sigaction_t, oldact: *void) -> s32 #foreign libc;
|
||||
|
||||
main_socket : Socket;
|
||||
|
||||
sigset_t :: struct { __val: [16] u64; }
|
||||
|
||||
siginfo_t :: struct { // This is not the correct size, but we don't instantiate it, so it's fine.
|
||||
si_signo: s32;
|
||||
si_errno: s32;
|
||||
si_code: s32;
|
||||
__pad0: s32;
|
||||
si_addr: *void;
|
||||
}
|
||||
|
||||
handle_signal :: (sig: s32, info: *siginfo_t, secret: *void) #c_call {
|
||||
c : #Context;
|
||||
push_context(c) {
|
||||
print("Graceful exit!!!\n");
|
||||
close_and_reset(*main_socket);
|
||||
}
|
||||
}
|
||||
main :: () {
|
||||
socket_init();
|
||||
main_socket = socket(AF_INET, .SOCK_STREAM, .TCP);
|
||||
reuse : s32 = 1;
|
||||
setsockopt(main_socket, SOL_SOCKET, SO_REUSEADDR, *reuse, size_of(s32));
|
||||
err := bind(main_socket, "127.0.0.1", 8080);
|
||||
if err != 0 {
|
||||
print("Error in binding socket: %\n", err);
|
||||
return;
|
||||
}
|
||||
err = listen(main_socket, 1);
|
||||
if err != 0 {
|
||||
print("Error in listening: %\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
sa: sigaction_t;
|
||||
sa.sa_sigaction = handle_signal;
|
||||
sigaction(2, *sa, null);
|
||||
|
||||
|
||||
while true {
|
||||
rbuf : [2048 * 1000]u8;
|
||||
addr : sockaddr;
|
||||
addr_len : socklen_t;
|
||||
reqs := accept(main_socket, *addr, *addr_len);
|
||||
bytesRead := recv(reqs, rbuf.data, rbuf.count, 0);
|
||||
datastring : string;
|
||||
datastring.count = bytesRead;
|
||||
datastring.data = rbuf.data;
|
||||
|
||||
req := parse_request(*datastring);
|
||||
res : Response;
|
||||
handler(req, *res);
|
||||
res_string := serialize_response(*res);
|
||||
send(reqs, res_string.data, xx res_string.count, 0);
|
||||
close_and_reset(*reqs);
|
||||
}
|
||||
}
|
||||
18
page.jai
Normal file
18
page.jai
Normal file
@ -0,0 +1,18 @@
|
||||
serve_page :: (res: *Response, content: string) {
|
||||
res.body = sprint("%\n%\n%", header, content, footer);
|
||||
}
|
||||
|
||||
#scope_file
|
||||
header : string = #string DONE
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<title>Omakase</title>
|
||||
</head>
|
||||
<body>
|
||||
DONE
|
||||
|
||||
footer : string = #string DONE
|
||||
</body>
|
||||
</html>
|
||||
DONE
|
||||
12
server.jai
Normal file
12
server.jai
Normal file
@ -0,0 +1,12 @@
|
||||
#load "style.jai";
|
||||
#load "page.jai";
|
||||
|
||||
handler :: (req: Request, res: *Response) {
|
||||
if req.route == {
|
||||
case "/";
|
||||
serve_page(res, "<h1>Hello from Jai!</h1>");
|
||||
return;
|
||||
case "/style.css";
|
||||
serve_stylesheet(res);
|
||||
}
|
||||
}
|
||||
24
style.jai
Normal file
24
style.jai
Normal file
@ -0,0 +1,24 @@
|
||||
serve_stylesheet :: (res: *Response) {
|
||||
set_header(res, "Content-Type", "text/stylesheet");
|
||||
res.body = stylesheet;
|
||||
}
|
||||
|
||||
stylesheet : string = #string DONE
|
||||
:root {
|
||||
--ink: #050505;
|
||||
--paper: #FAFAFA;
|
||||
--gold: #C5A059;
|
||||
--gold-dim: #E0D0B0;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:wght@452&family=Prata&display=swap');
|
||||
|
||||
body {
|
||||
font-family: "Prata", serif;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
background-color: var(--paper);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
DONE
|
||||
Loading…
x
Reference in New Issue
Block a user