From 2acf7fa873598003dd49ca69728140c913a4f3cd Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 8 Oct 2023 11:14:26 +0200 Subject: [PATCH] net::http: export new_request{,_body} --- cmd/http/main.ha | 41 +++++++++++++---------- net/http/client.ha | 75 ------------------------------------------ net/http/request.ha | 80 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 92 deletions(-) diff --git a/cmd/http/main.ha b/cmd/http/main.ha index 7d2fe78..82bdf35 100644 --- a/cmd/http/main.ha +++ b/cmd/http/main.ha @@ -7,37 +7,44 @@ use net::uri; use os; use strings; +const usage: [_]getopt::help = [ + "HTTP client", + ('H', "Name:value", "Sets an HTTP header"), + "url" +]; + export fn main() void = { const client = http::newclient("Hare net::http test client"); defer http::client_finish(&client); - const cmd = getopt::parse(os::args, - "HTTP client", - ('H', "Name:value", "Sets an HTTP header"), - "url"); + const cmd = getopt::parse(os::args, usage...); defer getopt::finish(&cmd); - let head = http::client_default_header(&client); - for (let i = 0z; i < len(cmd.opts); i += 1) { - const (opt, val) = cmd.opts[i]; - switch (opt) { - case 'H' => - const (name, val) = strings::cut(val, ":"); - http::header_add(head, name, val); - case => abort(); - }; + if (len(cmd.args) != 1) { + getopt::printusage(os::stderr, "http", usage)!; + os::exit(os::status::FAILURE); }; - const target = cmd.args[0]; - const target = match (uri::parse(target)) { + const targ = match (uri::parse(cmd.args[0])) { case let u: uri::uri => yield u; case uri::invalid => log::fatal("Invalid URI"); }; - defer uri::finish(&target); + defer uri::finish(&targ); - const resp = match (http::get(&client, &target)) { + let req = http::new_request(&client, "GET", &targ)!; + for (let i = 0z; i < len(cmd.opts); i += 1) { + const (opt, val) = cmd.opts[i]; + switch (opt) { + case 'H' => + const (name, val) = strings::cut(val, ":"); + http::header_add(&req.header, name, val); + case => abort(); + }; + }; + + const resp = match (http::do(&client, &req)) { case let err: http::error => log::fatal("HTTP error:", http::strerror(err)); case let resp: http::response => diff --git a/net/http/client.ha b/net/http/client.ha index 98fa8bb..26d3b68 100644 --- a/net/http/client.ha +++ b/net/http/client.ha @@ -1,10 +1,5 @@ -use errors; -use fmt; use io; -use net::ip; use net::uri; -use strconv; -use strings; export type client = struct { default_header: header, @@ -44,76 +39,6 @@ export fn client_default_transport(client: *client) *transport = { return &client.default_transport; }; -fn new_request( - client: *client, - method: str, - target: *uri::uri, -) (request | errors::unsupported) = { - let req = request { - method = method, - target = alloc(uri::dup(target)), - header = header_dup(&client.default_header), - transport = null, - body = void, - }; - switch (req.target.scheme) { - case "http" => - if (req.target.port == 0) { - req.target.port = 80; - }; - case "https" => - if (req.target.port == 0) { - req.target.port = 443; - }; - case => - return errors::unsupported; - }; - - let host = match (req.target.host) { - case let host: str => - yield host; - case let ip: ip::addr4 => - yield ip::string(ip); - case let ip: ip::addr6 => - static let buf: [64 + 2]u8 = [0...]; - yield fmt::bsprintf(buf, "[{}]", ip::string(ip)); - }; - - if (req.target.scheme == "http" && req.target.port != 80) { - host = fmt::asprintf("{}:{}", host, req.target.port); - } else if (target.scheme == "https" && target.port != 443) { - host = fmt::asprintf("{}:{}", host, req.target.port); - } else { - host = strings::dup(host); - }; - defer free(host); - header_add(&req.header, "Host", host); - - return req; -}; - -fn new_request_body( - client: *client, - method: str, - target: *uri::uri, - body: io::handle, -) (request | errors::unsupported) = { - let req = new_request(client, method, target)?; - req.body = body; - - const offs = match (io::seek(body, 0, io::whence::CUR)) { - case let off: io::off => - yield off; - case io::error => - header_add(&req.header, "Transfer-Encoding", "chunked"); - return req; - }; - const ln = io::seek(body, 0, io::whence::END)!; - io::seek(body, offs, io::whence::SET)!; - header_add(&req.header, "Content-Length", strconv::ztos(ln: size)); - return req; -}; - fn uri_origin_form(target: *uri::uri) uri::uri = { let target = *target; target.scheme = ""; diff --git a/net/http/request.ha b/net/http/request.ha index ec982bb..11747fa 100644 --- a/net/http/request.ha +++ b/net/http/request.ha @@ -1,5 +1,10 @@ +use errors; +use fmt; use io; +use net::ip; use net::uri; +use strconv; +use strings; // Stores state related to an HTTP request. // @@ -33,3 +38,78 @@ export fn request_finish(req: *request) void = { uri::finish(req.target); free(req.target); }; + +// Creates a new HTTP [[request]] using the given HTTP [[client]] defaults. +export fn new_request( + client: *client, + method: str, + target: *uri::uri, +) (request | errors::unsupported) = { + let req = request { + method = method, + target = alloc(uri::dup(target)), + header = header_dup(&client.default_header), + transport = null, + body = void, + }; + switch (req.target.scheme) { + case "http" => + if (req.target.port == 0) { + req.target.port = 80; + }; + case "https" => + if (req.target.port == 0) { + req.target.port = 443; + }; + case => + return errors::unsupported; + }; + + let host = match (req.target.host) { + case let host: str => + yield host; + case let ip: ip::addr4 => + yield ip::string(ip); + case let ip: ip::addr6 => + static let buf: [64 + 2]u8 = [0...]; + yield fmt::bsprintf(buf, "[{}]", ip::string(ip)); + }; + + if (req.target.scheme == "http" && req.target.port != 80) { + host = fmt::asprintf("{}:{}", host, req.target.port); + } else if (target.scheme == "https" && target.port != 443) { + host = fmt::asprintf("{}:{}", host, req.target.port); + } else { + host = strings::dup(host); + }; + defer free(host); + header_add(&req.header, "Host", host); + return req; +}; + +// Creates a new HTTP [[request]] using the given HTTP [[client]] defaults and +// the provided request body. +// +// If the provided I/O handle is seekable, the Content-Length header is added +// automatically. Otherwise, Transfer-Encoding: chunked will be used. +export fn new_request_body( + client: *client, + method: str, + target: *uri::uri, + body: io::handle, +) (request | errors::unsupported) = { + let req = new_request(client, method, target)?; + req.body = body; + + const offs = match (io::seek(body, 0, io::whence::CUR)) { + case let off: io::off => + yield off; + case io::error => + header_add(&req.header, "Transfer-Encoding", "chunked"); + return req; + }; + const ln = io::seek(body, 0, io::whence::END)!; + io::seek(body, offs, io::whence::SET)!; + header_add(&req.header, "Content-Length", strconv::ztos(ln: size)); + return req; +};