package http import "core:bufio" import "core:bytes" import "core:io" import "core:log" import "core:net" import "core:strconv" import "core:strings" import "core:unicode" @(private) method_from_str :: proc(method_str: string) -> Method { switch method_str { case "GET": return .GET case "DELETE": return .DELETE case "PUT": return .PUT case "POST": return .POST } unreachable() } @(private) str_from_content_type :: proc(content_type: ContentType) -> string { switch content_type { case .Html: return "text/html" case .Image: return "image/jpeg" } unreachable() } @(private) status_text :: proc(status: Status) -> string { switch status { case .Ok: return "OK" case .NotFound: return "Not found" case .BadRequest: return "Bad request" case .InternalServerError: return "Internal server error" } unreachable() } @(private) unmarshall_request_line :: proc( request: ^Request, reader: ^bufio.Reader, allocator := context.temp_allocator ) -> RequestError { request_line, io_err := bufio.reader_read_slice(reader, '\n') if io_err != nil do return .InvalidRequestLine request_line = sanitize_byte_slice(request_line) parts := bytes.split(request_line, {' '}, allocator) if len(parts) != 3 do return .InvalidRequestLine request.method = method_from_str(string(parts[0])) request.path = string(parts[1]) request.proto_version = string(parts[2]) return nil } @(private) unmarshall_request_headers :: proc( request: ^Request, reader: ^bufio.Reader, allocator := context.temp_allocator, ) -> RequestError { request.header = make(Header, allocator) for { header_line, io_err := bufio.reader_read_slice(reader, '\n') if io_err != nil do return .InvalidHeaderFormat header_line = sanitize_byte_slice(header_line) if len(header_line) == 0 do break parts := bytes.split_n(header_line, {':'}, 2, allocator) if len(parts) != 2 do return .InvalidHeaderFormat key := string(bytes.trim_space(parts[0])) value := string(bytes.trim_space(parts[1])) request.header[key] = value } return nil } @(private) unmarshall_request_body :: proc( request: ^Request, reader: ^bufio.Reader, allocator := context.temp_allocator, ) -> RequestError { content_length_str, ok := request.header["Content-Length"] if ok { content_length, ok := strconv.parse_uint(content_length_str) if !ok do return .InvalidHeaderFormat request.body = make([]byte, content_length, allocator) _, err := bufio.reader_read(reader, request.body) if err != nil do return .FailedParsing } else { request.body = {} } return nil } @(private) sanitize_byte_slice :: proc(slice: []byte) -> []byte { return bytes.trim(slice, {'\n', '\r'}) } unmarshall_request :: proc( data: []byte, allocator := context.temp_allocator, ) -> ( request: Request, request_err: RequestError, ) { if len(data) == 0 do return request, .IncompleteRequest byte_reader: bytes.Reader stream := bytes.reader_init(&byte_reader, data) reader: bufio.Reader bufio.reader_init(&reader, io.to_reader(stream), allocator = allocator) defer bufio.reader_destroy(&reader) unmarshall_request_line(&request, &reader) or_return unmarshall_request_headers(&request, &reader, allocator) or_return unmarshall_request_body(&request, &reader, allocator) or_return return request, nil } marshall_response :: proc( response: ^Response, allocator := context.temp_allocator, ) -> []byte { buffer: bytes.Buffer bytes.buffer_init_allocator(&buffer, 0, 0, allocator) bytes.buffer_write_string(&buffer, response.proto_version) bytes.buffer_write_string(&buffer, " ") status_code_buf := make([]byte, 4, allocator) status_code_str := strconv.write_uint( status_code_buf[:], u64(response.status), 10, ) bytes.buffer_write_string(&buffer, status_code_str) bytes.buffer_write_string(&buffer, " ") status_msg := status_text(response.status) bytes.buffer_write_string(&buffer, status_msg) bytes.buffer_write_string(&buffer, "\r\n") for key, value in response.header { bytes.buffer_write_string(&buffer, key) bytes.buffer_write_string(&buffer, ": ") bytes.buffer_write_string(&buffer, value) bytes.buffer_write_string(&buffer, "\r\n") } bytes.buffer_write_string(&buffer, "\r\n") bytes.buffer_write(&buffer, response.body) return bytes.buffer_to_bytes(&buffer) }