213 lines
6.4 KiB
Zig
213 lines
6.4 KiB
Zig
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);
|
|
}
|