summaryrefslogtreecommitdiff
path: root/backend/api/src/req.zig
diff options
context:
space:
mode:
Diffstat (limited to 'backend/api/src/req.zig')
-rw-r--r--backend/api/src/req.zig311
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;
+}