#+private
package http
import "core:bytes"
import "core:fmt"
import "core:mem"
import "core:net"
import "core:strconv"
import "core:sync"
import "core:sys/posix"
import "core:testing"
import "core:time"
import "../../container/ring_buffer"
SHOULD_QUIT := false
should_quit_handler :: proc "c" (sig: posix.Signal) {
SHOULD_QUIT = true
}
find_crlf :: proc {
find_crlf_bytes,
find_crlf_ring_buffer,
}
find_double_crlf :: proc {
find_double_crlf_bytes,
find_double_crlf_ring_buffer,
}
@(test)
test_ca_find_double_crlf_at_start :: proc(t: ^testing.T) {
rb: ring_buffer.Ring_Buffer
ring_buffer.init(&rb)
defer ring_buffer.destroy(&rb)
ring_buffer.append(&rb, {'F', 'o', 'o', 'b', 'a', 'r', '\r', '\n', '\r', '\n'})
i := find_double_crlf_ring_buffer(&rb)
testing.expect(t, i >= 0, "couldn't find double_crlf")
}
@(test)
test_ca_find_double_crlf_over_border :: proc(t: ^testing.T) {
rb: ring_buffer.Ring_Buffer
ring_buffer.init(&rb)
defer ring_buffer.destroy(&rb)
rb.off += rb.cap - 5
ring_buffer.append(&rb, {'F', 'o', 'o', 'b', 'a', 'r', '\r', '\n', '\r', '\n'})
i := find_double_crlf_ring_buffer(&rb)
testing.expect(t, i >= 0, "couldn't find double_crlf")
}
Conn :: struct {
sock: net.TCP_Socket,
carry: ring_buffer.Ring_Buffer,
}
send_response_header :: proc(sock: net.TCP_Socket, res: ^Response, should_close: bool) -> bool {
if _, ok := headers_get_first(res.headers[:], "content-type"); !ok {
headers_add(&res.headers, "content-type", "text/html; charset=utf-8")
}
if _, ok := headers_get_first(res.headers[:], "content-length"); !ok {
buf: [10]u8
v := len(res.body) if res.body != nil else 0
headers_add(&res.headers, "content-length", strconv.write_int(buf[:], i64(v), 10))
}
defer {
for _, i in res.headers {
delete(res.headers[i].name)
delete(res.headers[i].value)
}
delete(res.headers)
}
statusMessage := Status_Message
// Build response head
b: bytes.Buffer
defer bytes.buffer_destroy(&b)
bytes.buffer_write_string(&b, "HTTP/1.1 ")
bytes.buffer_write_string(&b, statusMessage[res.code])
bytes.buffer_write_string(&b, "\r\n")
for h in res.headers {
bytes.buffer_write_string(&b, h.name)
bytes.buffer_write_string(&b, ": ")
bytes.buffer_write_string(&b, h.value)
bytes.buffer_write_string(&b, "\r\n")
}
bytes.buffer_write_string(&b, "Connection: ")
bytes.buffer_write_string(&b, "close" if should_close else "keep-alive")
bytes.buffer_write_string(&b, "\r\n")
bytes.buffer_write_string(&b, "\r\n")
return send_response(sock, bytes.buffer_to_bytes(&b))
}
send_response :: proc {
send_response_bytes,
//send_response_stream,
}
// TODO: Overrideable to let user maybe handle it themself
send_response_error :: proc(sock: net.TCP_Socket, code: Status_Code) {
res := Response {
code = code,
}
send_response_header(sock, &res, true)
}
send_response_bytes :: proc(sock: net.TCP_Socket, buf: []u8) -> bool {
off: int
for off < len(buf) {
n, err := net.send_tcp(sock, buf[off:])
if err != nil {
return false
}
off += n
}
return true
}
/*send_response_body_stream :: proc(...) {
}*/
READ_CHUNK :: 4096
read_into_carry :: proc(c: ^Conn) -> (ok: bool, timeout: bool) {
tmp: [READ_CHUNK]u8
n, err := net.recv_tcp(c.sock, tmp[:])
if err != nil || n == 0 {
return false, err == .Would_Block
}
ring_buffer.append(&c.carry, tmp[:n])
return true, false
}
find_crlf_bytes :: proc(b: []u8) -> int {
if len(b) < 2 {
return -1
}
for i := 0; i <= len(b) - 2; i += 2 {
if b[i] == '\r' && b[i + 1] == '\n' {
return i
}
}
return -1
}
find_crlf_ring_buffer :: proc(ca: ^ring_buffer.Ring_Buffer) -> int {
if ca.len < 2 {
return -1
}
for i := 0; i <= ca.len - 2; i += 1 {
if ca.buf[(ca.off + i + 0) % ca.cap] == '\r' && ca.buf[(ca.off + i + 1) % ca.cap] == '\n' {
return i
}
}
return -1
}
find_double_crlf_bytes :: proc(b: []u8) -> int {
if len(b) < 4 {
return -1
}
for i := 0; i <= len(b) - 4; i += 4 {
if b[i] == '\r' && b[i + 1] == '\n' && b[i + 2] == '\r' && b[i + 3] == '\n' {
return i
}
}
return -1
}
find_double_crlf_ring_buffer :: proc(ca: ^ring_buffer.Ring_Buffer) -> int {
if ca.len < 4 {
return -1
}
for i := 0; i <= ca.len - 4; i += 1 {
if ca.buf[(ca.off + i + 0) % ca.cap] == '\r' &&
ca.buf[(ca.off + i + 1) % ca.cap] == '\n' &&
ca.buf[(ca.off + i + 2) % ca.cap] == '\r' &&
ca.buf[(ca.off + i + 3) % ca.cap] == '\n' {
return i
}
}
return -1
}
MAX_HEADER_BYTES :: 32 * 1024
MAX_BODY_BYTES :: 10 * 1024 * 1024
read_head_from_conn :: proc(c: ^Conn) -> (head: []u8, ok: bool, timeout: bool) {
for {
if c.carry.len > MAX_HEADER_BYTES {
return nil, false, false
}
i := find_double_crlf(&c.carry)
if i > 0 {
head = ring_buffer.consume_front(&c.carry, i + 4)
return head, true, false
}
if ok, timeout = read_into_carry(c); !ok {
return nil, false, timeout
}
}
}
read_body_from_conn :: proc(c: ^Conn, want: int) -> (body: []u8, ok: bool, timeout: bool) {
if want < 0 || want > MAX_BODY_BYTES {
return nil, false, false
}
if want == 0 {
return nil, true, false
}
for c.carry.len < want {
if ok, timeout = read_into_carry(c); !ok {
return nil, false, timeout
}
if c.carry.len > MAX_BODY_BYTES {
return nil, false, false
}
}
body = ring_buffer.consume_front(&c.carry, want)
return body, true, false
}
parse_request :: proc(req: ^Request) -> bool {
if req._raw_head == nil {
return false
}
raw_head_block := req._raw_head[:len(req._raw_head) - 4]
head_i := find_crlf(raw_head_block)
if head_i <= 0 {
return false
}
read_head_line(req, req._raw_head[:head_i])
read_headers(req, req._raw_head[head_i + 2:])
host_header, h_ok := headers_get_first(req.headers[:], "host")
if !h_ok {
if req.version == "HTTP/1.1" {
return false
}
} else {
req.host = host_header
}
cl_header, cl_ok := headers_get_first(req.headers[:], "content-length")
if cl_ok {
ok: bool
if req.content_length, ok = strconv.parse_int(cl_header); !ok {
return false
}
}
return true
}
read_head_line :: proc(req: ^Request, head_line: []u8) -> bool {
off, i: int
i = bytes.index_byte(head_line[off:], ' ')
if i <= 0 {
return false
}
method := string(head_line[off:off + i])
off += i + 1
i = bytes.index_byte(head_line[off:], ' ')
if i <= 0 {
return false
}
target := string(head_line[off:off + i])
off += i + 1
version := string(head_line[off:])
switch method {
case "GET":
req.method = .Get
case "POST":
req.method = .Post
case "PUT":
req.method = .Put
case "DELETE":
req.method = .Delete
case "PATCH":
req.method = .Patch
case "HEAD":
req.method = .Head
case "OPTIONS":
req.method = .Options
case:
fmt.eprintf("unknown method: %s\n", method)
return false
}
req.target = target
req.version = version
return true
}
read_headers :: proc(req: ^Request, header_block: []u8) {
off: int
i := find_crlf(header_block)
for i < len(header_block) && off < i {
line := header_block[off:i]
if j := bytes.index_byte(line, ':'); j >= 0 {
headers_add(&req.headers, string(line[:j]), string(line[j + 1:]))
}
i = off + find_crlf(header_block[off:])
off = i + 2
}
}
should_close_connection :: proc(req: ^Request) -> bool {
if header_has_token(req.headers[:], "connection", "close") {
return true
}
if req.version == "HTTP/1.0" {
return !header_has_token(req.headers[:], "connection", "keep-alive")
}
return false
}
MAX_REQUESTS_PER_CONN :: 100
Conn_Job :: struct {
conn: Conn,
handler: Handler,
user_data: rawptr,
active_connections: ^int,
}
// NOTE: Takes ownership of Conn_Job
handle_conn_job :: proc(cj: ^Conn_Job) {
main_allocator := context.allocator
when ODIN_DEBUG {
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
}
when ODIN_DEBUG {
fmt.printf("debug: ### enter conn_handle (n: %d/%d)\n", cj.active_connections^, MAX_CONNECTIONS)
}
conn_handle(&cj.conn, cj.handler, cj.user_data)
sync.atomic_sub(cj.active_connections, 1)
net.close(cj.conn.sock)
free(cj, main_allocator)
when ODIN_DEBUG {
fmt.printf("debug: ### leave conn_handle (n: %d/%d)\n", cj.active_connections^, MAX_CONNECTIONS)
}
when ODIN_DEBUG {
for _, leak in track.allocation_map {
fmt.printf("%v leaked %m\n", leak.location, leak.size)
}
mem.tracking_allocator_destroy(&track)
}
}
MAX_IDLE_TIMEOUT :: 15 * time.Second
conn_handle :: proc(c: ^Conn, req_handler: Handler, user_data: rawptr) {
net.set_option(c.sock, .Receive_Timeout, MAX_IDLE_TIMEOUT)
ring_buffer.init(&c.carry, 10)
defer ring_buffer.destroy(&c.carry)
for req_count := 0; req_count < MAX_REQUESTS_PER_CONN; req_count += 1 {
raw_head_bytes, ok, timeout := read_head_from_conn(c)
if !ok {
if timeout {
send_response_error(c.sock, .RequestTimeout)
}
return
}
req: Request
req._raw_head = raw_head_bytes
req.headers = make([dynamic]Header, 0, 4)
if !parse_request(&req) {
send_response_error(c.sock, .BadRequest)
if req.host != "" {
delete(req.host)
}
if req.headers != nil {
for _, i in req.headers {
delete(req.headers[i].name)
delete(req.headers[i].value)
}
delete(req.headers)
}
if req._raw_head != nil {
delete(req._raw_head)
}
if req.body != nil {
delete(req.body)
}
return
}
should_close := should_close_connection(&req)
defer {
for _, i in req.headers {
delete(req.headers[i].name)
delete(req.headers[i].value)
}
delete(req.headers)
}
defer delete(req._raw_head)
if req.content_length > 0 {
req.body, ok, timeout = read_body_from_conn(c, req.content_length)
if !ok {
if timeout {
send_response_error(c.sock, .RequestTimeout)
}
return
}
}
defer delete(req.body)
_, te_ok := headers_get_first(req.headers[:], "Transfer-Encoding")
if te_ok {
send_response_error(c.sock, .NotImplemented)
return
}
// --- HANDLER CODE ---
when ODIN_DEBUG {
fmt.printf("debug: --> %s %s %s\n", req.method, req.target, req.version)
fmt.printf("debug: headers -> %v\n", req.headers)
// fmt.printf("debug: body -> %s\n", req.body)
}
res: Response
res.headers = make([dynamic]Header, 0, 4)
req_handler(req, &res, user_data)
statusMessage := Status_Message
when ODIN_DEBUG {
fmt.printf("debug: <-- %s %s\n", req.target, statusMessage[res.code])
}
if !send_response_header(c.sock, &res, should_close) {
fmt.eprintf("send header: error\n")
return
}
if res.body != nil {
if !send_response(c.sock, res.body) {
fmt.eprintf("send body: error\n")
return
}
delete(res.body)
}
free_all(context.temp_allocator)
if should_close {
break
}
}
when ODIN_DEBUG {
fmt.printf("debug: #!#! connection closed !#!#\n")
}
}