zig-pay/src/server.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);
}