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; }