diff options
Diffstat (limited to 'backend/api/src/req.zig')
| -rw-r--r-- | backend/api/src/req.zig | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/backend/api/src/req.zig b/backend/api/src/req.zig new file mode 100644 index 0000000..90e5591 --- /dev/null +++ b/backend/api/src/req.zig @@ -0,0 +1,311 @@ +const z = @import( "std" ); +const u = @import( "util.zig" ); +const HttpClient = @import( "tls12" ); + +const ClientResponse = HttpClient.Response; +const ClientRequest = HttpClient.Request; + +const ArenaAllocator = z.heap.ArenaAllocator; +const Allocator = z.mem.Allocator; +const print = z.debug.print; + +pub const Header = z.http.Header; + +pub const OnChunkFunc = *const fn ( []const u8 ) void; +pub fn OnChunkFuncWithData( t: type ) type { + return *const fn ( []const u8, t ) void; +} +pub const OnReqEndFunc = *const fn( *Result ) void; +pub fn OnReqEndFuncWithData( t: type ) type { + return *const fn( *Result, t ) void; +} + + +const ParamsBase = struct { + url: []const u8, + body: ?[]const u8 = null, + method: z.http.Method = z.http.Method.GET, + headers: ?[]const Header = null, +}; + +pub fn Params() type { + return u.MergeTypes( ParamsBase, struct { chunk_fn: ?OnChunkFunc = null } ); +} +pub fn ParamsAsync() type { + return u.MergeTypes( ParamsBase, struct { end_fn: ?OnReqEndFunc = null, chunk_fn: ?OnChunkFunc = null, mutex: z.Thread.Mutex = .{} } ); +} +pub fn ParamsWithData( t: type ) type { + return u.MergeTypes( ParamsBase, struct { chunk_fn: ?OnChunkFuncWithData(t), chunk_data: t = undefined } ); +} +pub fn ParamsWithDataAsync( t: type ) type { + return u.MergeTypes( ParamsBase, struct { end_fn: ?OnReqEndFuncWithData(t), chunk_fn: ?OnChunkFuncWithData(t), chunk_data: t = undefined, mutex: z.Thread.Mutex = .{} } ); +} + + +pub const Result = struct { + ok: bool = false, + url: []const u8 = &[_]u8{ 0 }, + status: z.http.Status = z.http.Status.teapot, + body: ?[]const u8 = null, + headers: z.ArrayList( z.http.Header ), + alloc: z.mem.Allocator = undefined, + + pub fn deinit( self: *const Result ) void { + if( self.body ) |*body| + self.alloc.free( body.* ); + + self.headers.deinit(); + } +}; + +pub fn send( params: Params(), a: Allocator ) Result { + var ret = Result{ + .alloc = a, + .url = params.url, + .headers = z.ArrayList( Header ).init( a ) + }; + + const uri = z.Uri.parse( params.url ) catch return ret; + var client = HttpClient{ .allocator = a }; + defer client.deinit(); + + const header_buf: []u8 = a.alloc( u8, 1024 * 8 ) catch return ret; + defer a.free( header_buf ); + + var req = client.open( params.method, uri, .{ + .server_header_buffer = header_buf + }) catch |e| { z.debug.print( "{any}\n", .{ e } ); return ret; }; + defer req.deinit(); + + if( params.headers ) |hdrs| + req.extra_headers = hdrs; + + if( params.body != null and params.method == z.http.Method.POST ) + req.transfer_encoding = .{ .content_length = params.body.?.len }; + + req.send() catch return ret; + if( params.body != null and params.method == z.http.Method.POST ) { + var writer = req.writer(); + writer.writeAll( params.body.? ) catch return ret; + } + req.finish() catch return ret; + req.wait() catch return ret; + + ret.ok = true; + if( params.chunk_fn != null ) { + receiveChunked( &ret, &req, params, a ) catch { ret.ok = false; }; + } else { + receiveSingle( &ret, &req, a ) catch { ret.ok = false; }; + } + + receiveHeaders( &ret, &req ); + return ret; +} + +///compared to send, takes an additional data arg that gets passed to the chunk_fn +pub fn sendWithData( t: type, params: ParamsWithData(t), a: Allocator ) Result { + var ret = Result{ + .alloc = a, + .url = params.url, + .headers = z.ArrayList( Header ).init( a ) + }; + + const uri = z.Uri.parse( params.url ) catch return ret; + var client = HttpClient{ .allocator = a }; + defer client.deinit(); + + const header_buf: []u8 = a.alloc( u8, 1024 * 8 ) catch return ret; + defer a.free( header_buf ); + + var req = client.open( params.method, uri, .{ + .server_header_buffer = header_buf + }) catch |e| { z.debug.print( "http error: {any}\n", .{ e } ); return ret; }; + defer req.deinit(); + + if( params.headers ) |hdrs| + req.extra_headers = hdrs; + + if( params.body != null and params.method == z.http.Method.POST ) + req.transfer_encoding = .{ .content_length = params.body.?.len }; + + req.send() catch return ret; + if( params.body != null and params.method == z.http.Method.POST ) { + var writer = req.writer(); + writer.writeAll( params.body.? ) catch return ret; + } + req.finish() catch return ret; + req.wait() catch return ret; + + receiveHeaders( &ret, &req ); + if( params.chunk_fn != null ) { + receiveChunkedWithData( t, &ret, &req, params, a ) catch { ret.ok = false; }; + } else { + receiveSingle( &ret, &req, a ) catch { ret.ok = false; }; + } + + ret.ok = req.response.status == .ok; + return ret; +} + +///UNFINISHED: DOES NOT WORK YET +pub fn dispatchSendWithData( t: type, params: ParamsWithDataAsync(t), _a: ArenaAllocator ) void { + var arena = _a; + const a = arena.allocator(); + defer arena.deinit(); + var ret = Result{ + .alloc = a, + .url = params.url, + .headers = z.ArrayList( Header ).init( a ) + }; + + defer if( params.end_fn ) |f| f( &ret, params.chunk_data ); + + const uri = z.Uri.parse( params.url ) catch return; + var client = HttpClient{ .allocator = a }; + + const header_buf: []u8 = a.alloc( u8, 1024 * 8 ) catch return; + + var req = client.open( params.method, uri, .{ + .server_header_buffer = header_buf, + }) catch |e| { z.debug.print( "http error: {any}\n", .{ e } ); return; }; + + if( params.headers ) |hdrs| + req.extra_headers = hdrs; + + if( params.body != null and params.method == z.http.Method.POST ) + req.transfer_encoding = .{ .content_length = params.body.?.len }; + + req.send() catch return; + if( params.body != null and params.method == z.http.Method.POST ) { + var writer = req.writer(); + writer.writeAll( params.body.? ) catch return; + } + req.finish() catch return; + req.wait() catch return; + + receiveHeaders( &ret, &req ); + if( params.chunk_fn != null ) { + receiveChunkedWithDataAsync( t, &ret, &req, params, a ) catch { ret.ok = false; }; + } else { + receiveSingle( &ret, &req, a ) catch { ret.ok = false; }; + } + + ret.ok = req.response.status == .ok; +} + +pub fn sendWithDataAsync( t: type, params: ParamsWithDataAsync(t), _arena: ArenaAllocator ) void { + var arena = _arena; + const a = arena.allocator(); + const paramsc = params; + + _=z.Thread.spawn( .{ .allocator = a }, dispatchSendWithData, .{ t, paramsc, arena } ) catch |e| { + arena.deinit(); + z.debug.print( "error spawning thread: {any}\n", .{ e } ); return; + }; +} + +fn receiveHeaders( ret: *Result, req: *ClientRequest ) void { + var iter = req.response.iterateHeaders(); + while( iter.next() ) |header| + ret.headers.append( header ) catch continue; +} + +fn receiveSingle( ret: *Result, res: *ClientRequest, a: Allocator ) !void { + ret.status = res.response.status; + // fuck it we ball + const slice = try res.reader().readAllAlloc( a, 1024 * 1024 ); + ret.body = slice; + if( ret.status != .ok ) + ret.ok = false; +} + +fn receiveChunked( ret: *Result, req: *ClientRequest, q: Params(), a: Allocator ) !void { + ret.status = req.response.status; + + var full_str = z.ArrayList( u8 ).init( a ); + defer full_str.deinit(); + const writer = full_str.writer(); + + var buf = try a.alloc( u8, 1024 * 16 ); + defer a.free( buf ); + + var bytes_total: usize = 0; + const reader = req.reader(); + while( true ) { + const bytes_read = try reader.read( buf ); + if( bytes_read == 0 ) + break; + + bytes_total += bytes_read; + _=try writer.write( buf[0..bytes_read] ); + const f = q.chunk_fn; + if( f ) |func| func( buf[0..bytes_read] ); + z.Thread.yield() catch {}; + } + + const slice = try a.alloc( u8, bytes_total ); + @memcpy( slice[0..bytes_total], full_str.items[0..bytes_total] ); + ret.body = slice; +} + +fn receiveChunkedWithData( t: type, ret: *Result, req: *ClientRequest, q: ParamsWithData(t), a: Allocator ) !void { + ret.status = req.response.status; + + var full_str = z.ArrayList( u8 ).init( a ); + defer full_str.deinit(); + const writer = full_str.writer(); + + var buf = try a.alloc( u8, 1024 * 16 ); + defer a.free( buf ); + + var bytes_total: usize = 0; + const reader = req.reader(); + + //todo: throw this into another thread + while( true ) { + const bytes_read = try reader.read( buf ); + if( bytes_read == 0 ) + break; + + bytes_total += bytes_read; + _=try writer.write( buf[0..bytes_read] ); + const f = q.chunk_fn; + if( f ) |func| func( buf[0..bytes_read], q.chunk_data ); + z.Thread.yield() catch {}; + } + + const slice = try a.alloc( u8, bytes_total ); + @memcpy( slice[0..bytes_total], full_str.items[0..bytes_total] ); + ret.body = slice; +} + +fn receiveChunkedWithDataAsync( t: type, ret: *Result, req: *ClientRequest, q: ParamsWithDataAsync(t), a: Allocator ) !void { + ret.status = req.response.status; + + var full_str = z.ArrayList( u8 ).init( a ); + defer full_str.deinit(); + const writer = full_str.writer(); + + var buf = try a.alloc( u8, 1024 * 16 ); + defer a.free( buf ); + + var bytes_total: usize = 0; + const reader = req.reader(); + + //todo: throw this into another thread + while( true ) { + const bytes_read = try reader.read( buf ); + if( bytes_read == 0 ) + break; + + bytes_total += bytes_read; + _=try writer.write( buf[0..bytes_read] ); + const f = q.chunk_fn; + if( f ) |func| func( buf[0..bytes_read], q.chunk_data ); + z.Thread.yield() catch {}; + } + + const slice = try a.alloc( u8, bytes_total ); + @memcpy( slice[0..bytes_total], full_str.items[0..bytes_total] ); + ret.body = slice; +} |
