const std = @import("std"); const endpoints = @import("endpoints"); const http = std.http; const net = std.net; const log = std.log; const expect = std.testing.expect; const status = std.http.Status; const method = std.http.Method; const time = std.time; const mem = std.mem; const EndpointsManager = endpoints.EndpointsManager; const Request = endpoints.Request; const Response = endpoints.Response; const HEADER_SIZE: usize = 1024; const BODY_SIZE: usize = 1024; const RESPONSE_SIZE: usize = 1024; const SEND_BUFFER_SIZE: usize = 1024; fn separatePathAndQuery(target: []const u8) [2][]const u8 { for (0..target.len) |i| { if (target[i] == '?') return .{ target[0..i], target[i..], }; } return .{ target[0..], &[_]u8{} }; } pub const HttpServer = struct { address: net.Address, address_options: net.Address.ListenOptions, get_endpoints: *EndpointsManager, post_endpoints: *EndpointsManager, put_endpoints: *EndpointsManager, pub fn init(ip_map: []const u8, port: u16, get_endpoints: *EndpointsManager, post_endpoints: *EndpointsManager, put_endpoints: *EndpointsManager) !HttpServer { const address = try net.Address.parseIp4(ip_map, port); return HttpServer{ .address = address, .address_options = net.Address.ListenOptions{}, .get_endpoints = get_endpoints, .post_endpoints = post_endpoints, .put_endpoints = put_endpoints, }; } pub fn start(self: *HttpServer) !void { var server = try self.address.listen(self.address_options); log.info("Listeting server http://{}\n", .{self.address}); defer server.deinit(); var count: usize = 0; while (true) { const conn = server.accept() catch |err| { log.err("Error socket {}\n", .{err}); continue; }; var thread = std.Thread.spawn(.{ .stack_size = 1024 * 6 }, handleConnection, .{ self, conn }) catch |err| { log.err("Creating thread error: {}\n", .{err}); conn.stream.close(); continue; }; count += 1; thread.detach(); } } fn handleConnection(self: *HttpServer, conn: net.Server.Connection) void { defer conn.stream.close(); var buffer_request: [HEADER_SIZE]u8 = undefined; var buffer_response: [RESPONSE_SIZE]u8 = undefined; var send_buffer: [SEND_BUFFER_SIZE]u8 = undefined; var response: Response = undefined; var req_endpoint: Request = undefined; while (true) { var req_len: usize = 0; var lb_weight: usize = 0; while (true) { const aux_len = conn.stream.read(buffer_request[req_len..]) catch { return; }; if (aux_len == 0) { return; } lb_weight = if (buffer_request[0] == 0) 1 else 0; req_len += aux_len; if (buffer_request[0 + lb_weight] == 'G' or buffer_request[7 + lb_weight] == 'u') break; if (req_len >= 25 + 22 + 30 + 20 + 32 + 2 + 70) { break; } } const b_target = nextWhiteSpace(&buffer_request, 0 + lb_weight, req_len) + 1; const e_target = nextWhiteSpace(&buffer_request, b_target, req_len); const full_path = buffer_request[b_target..e_target]; const path, const query = separatePathAndQuery(full_path); const http_method: http.Method = switch (buffer_request[0 + lb_weight]) { 'P' => .POST, 'G' => .GET, else => unreachable }; req_endpoint = Request{ .method = http_method, .path = path, .query = query, .body = if (http_method == .POST) buffer_request[findBeginJson(&buffer_request)..req_len] else buffer_request[0..0], }; response = Response{ .status = .service_unavailable, .content = &.{}, .buffer_response = &buffer_response, .chunked_content = .{ .buffer_chunk = &buffer_response }, }; self.handleReqRes(&req_endpoint, &response); const response_message = std.fmt.bufPrint( &send_buffer, "HTTP/1.1 200 OK\r\nContent-Length: {d}\r\n\r\n{s}", .{ response.content.len, response.content }, ) catch return; var res_len = response_message.len; if (lb_weight == 1) { send_buffer[res_len] = 0; res_len += 1; } conn.stream.writeAll(send_buffer[0..res_len]) catch return; } } fn nextWhiteSpace(buf: []const u8, b: usize, e: usize) usize { for (b..e) |i| if (buf[i] == ' ') return i; return b; } fn findBeginJson(buf: []const u8) usize { for (0..buf.len) |i| { if (buf[i] == '{' and (i > 0 and buf[i - 1] == '\n')) { return i; } } return if (buf.len == 0) 0 else buf.len - 1; } pub fn handleReqRes(self: HttpServer, req: *Request, res: *Response) void { switch (req.method) { .GET => resolveEndpoint(req, res, self.get_endpoints), .POST => resolveEndpoint(req, res, self.post_endpoints), .PUT => resolveEndpoint(req, res, self.put_endpoints), else => res.withStatus(.method_not_allowed).end(), } } pub fn resolveEndpoint(request: *Request, res: *Response, method_endpoints: *EndpointsManager) void { const path = request.path; const func = method_endpoints.getEndpointFunction(path); if (func != null) return func.?(request, res); log.warn("function not found to path {s}", .{path}); res.withStatus(.not_found).end(); } }; test "expect empty query when target don't have query" { const target = "/"; const path, const query = separatePathAndQuery(target); try expect(path.len == 1); try expect(query.len == 0); } test "expect separate query and path" { const target = "/path?query=value"; const path, const query = separatePathAndQuery(target); try expect(path.len == 5); try expect(query.len == 12); }