package http
import "core:fmt"
import "core:net"
import "core:os"
import "core:slice"
import "core:strings"
import "core:sync"
import "core:sys/posix"
import "core:thread"
import "core:time"
Method :: enum {
Get,
Post,
Put,
Delete,
Patch,
Head,
Options,
}
Status_Code :: enum {
Ok = 200,
BadRequest = 400,
NotFound = 404,
RequestTimeout = 408,
InternalServerError = 500,
NotImplemented = 501,
ServiceUnavailable = 503,
}
Status_Message :: #sparse[Status_Code]string {
.Ok = "200 OK",
.BadRequest = "400 Bad Request",
.NotFound = "404 Not Found",
.RequestTimeout = "408 Request Timeout",
.InternalServerError = "500 Internal Server Error",
.NotImplemented = "501 Not Implemented",
.ServiceUnavailable = "503 Service Unavailable",
}
Request :: struct {
_raw_head: []u8,
host: string,
method: Method,
target: string, // TODO: Implement URI type
version: string,
headers: [dynamic]Header,
content_length: int,
body: []u8,
}
Response :: struct {
code: Status_Code,
headers: [dynamic]Header,
body: []u8,
}
Handler :: proc(req: Request, res: ^Response, user_data: rawptr)
// TODO: stream support when body grows very large?
respond :: proc {
respond_empty,
respond_string,
respond_bytes,
//respond_stream,
}
respond_empty :: proc(res: ^Response, code: Status_Code) {
res.code = code
res.body = nil
}
respond_string :: proc(res: ^Response, code: Status_Code, str: string) {
res.code = code
res.body = slice.clone(transmute([]u8)str[:])
}
respond_bytes :: proc(res: ^Response, code: Status_Code, buf: []u8) {
res.code = code
res.body = slice.clone(buf)
}
/*respond_stream :: proc(res: ^Response, code: Status_Code, ...) {
}*/
MAX_CONNECTIONS :: 128
serve :: proc(
endpoint: net.Endpoint,
h: proc(req: Request, res: ^Response, user_data: rawptr),
data: rawptr,
) {
posix.signal(.SIGINT, should_quit_handler)
posix.signal(.SIGTERM, should_quit_handler)
listener, err := net.listen_tcp(endpoint)
if err != nil {
fmt.eprintf("listen_tcp failed: %v", err)
os.exit(1)
}
defer net.close(listener)
net.set_option(listener, .Receive_Timeout, time.Millisecond * 10)
fmt.printf("Glaze listening on %s\n", net.to_string(endpoint))
free_all(context.temp_allocator)
active_connections: int
for !SHOULD_QUIT {
client, _, err := net.accept_tcp(listener)
if err != nil {
if err == .Would_Block || err == .Interrupted {
continue
}
fmt.eprintf("accept_tcp failed: %v\n", err)
os.exit(1)
// continue
}
if active_connections >= MAX_CONNECTIONS {
send_response_error(client, .ServiceUnavailable)
net.close(client)
continue
}
sync.atomic_add(&active_connections, 1)
cj := new(Conn_Job)
cj.conn = {
sock = client,
}
cj.handler = h
cj.user_data = data
cj.active_connections = &active_connections
thread.create_and_start_with_poly_data(cj, handle_conn_job, context, self_cleanup = true)
}
}
// allocates only if escapeable character found
escape_html :: proc(s: string) -> (string, bool) {
escape_with := make(map[rune]string)
defer delete(escape_with)
escape_with['&'] = "&"
escape_with['<'] = "<"
escape_with['>'] = ">"
escape_with['"'] = """
escape_with['\''] = "'"
needs_escaping: bool
for c in s {
if c in escape_with {
needs_escaping = true
}
}
if needs_escaping {
b: strings.Builder
strings.builder_init(&b)
for c in s {
if c in escape_with {
strings.write_string(&b, escape_with[c])
} else {
strings.write_rune(&b, c)
}
}
return strings.to_string(b), true
}
return s, false
}