From 89173069e85416539b1256ed1ae1c77e89550ae6 Mon Sep 17 00:00:00 2001 From: Julian Hurst Date: Sun, 10 Mar 2024 02:35:08 +0100 Subject: Allow retrieving in memory response struct, and cleanup --- build.sh | 2 +- cmd/main.ha | 25 +++++++++++++++ curl.ha | 70 ----------------------------------------- internal/curl/README | 1 + internal/curl/ccurl/ccurl.ha | 47 ++++++++++++++++++++++++++++ internal/curl/curl.ha | 74 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 71 deletions(-) create mode 100644 cmd/main.ha delete mode 100644 curl.ha create mode 100644 internal/curl/README create mode 100644 internal/curl/ccurl/ccurl.ha create mode 100644 internal/curl/curl.ha diff --git a/build.sh b/build.sh index 90df2ad..95b7e58 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,3 @@ #!/bin/sh -hare build $(pkg-config --libs-only-L --libs-only-l libcurl) curl.ha +hare build $(pkg-config --libs-only-L --libs-only-l libcurl) -o hacurl cmd/main.ha diff --git a/cmd/main.ha b/cmd/main.ha new file mode 100644 index 0000000..95d1cad --- /dev/null +++ b/cmd/main.ha @@ -0,0 +1,25 @@ +use fmt; +use os; +use internal::curl; +use memio; +use io; + +export fn main() void = { + if (len(os::args) != 2) { + fmt::fatalf("USAGE: {} ", os::args[0]); + }; + let resp = curl::newresponse(); + defer curl::closeresponse(resp)!; + match (curl::get(os::args[1], &resp)) { + case void => + yield; + case let err: curl::setopterr => + fmt::fatalf("setopt returned {} instead of 0", err: int); + case let err: curl::performerr => + fmt::fatalf("perform returned {} instead of 0", err: int); + case let err: curl::getinfoerr => + fmt::fatalf("getinfo returned {} instead of 0", err: int); + }; + fmt::print(memio::string(&resp.data)!)!; + fmt::printfln("status: {}, size: {}", resp.status, resp.sz)!; +}; diff --git a/curl.ha b/curl.ha deleted file mode 100644 index 4c0c56d..0000000 --- a/curl.ha +++ /dev/null @@ -1,70 +0,0 @@ -use types::c; -use fmt; -use os; - -export type CURL = opaque; -export type CURLcode = int; -export type CURLINFO = int; - -def CURLOPT_URL = 10002; - -def CURLINFO_LONG = 0x200000; -def CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2; - -export @symbol("curl_easy_init") fn curl_easy_init() nullable *CURL; -export @symbol("curl_easy_cleanup") fn curl_easy_cleanup(handle: nullable *CURL) void; -export @symbol("curl_easy_setopt") fn curl_easy_setopt( - handle: nullable *CURL, - option: int, - parameter: const *c::char -) CURLcode; - -export @symbol("curl_easy_perform") fn curl_easy_perform(easy_handle: nullable *CURL) CURLcode; -export @symbol("curl_easy_getinfo") fn curl_easy_getinfo( - easy_handle: nullable *CURL, - info: CURLINFO, - code: nullable *c::long -) CURLcode; - -type curlerror = !(setopterr | performerr | getinfoerr); - -type setopterr = !int; -type performerr = !int; -type getinfoerr = !int; - -fn get(url: const str) (void | curlerror) = { - let c = curl_easy_init(); - defer curl_easy_cleanup(c); - let c_url = c::fromstr(url); - defer free(c_url); - let res = curl_easy_setopt(c, CURLOPT_URL, c_url); - if (res != 0) { - return res: setopterr; - }; - res = curl_easy_perform(c); - if (res != 0) { - return res: performerr; - }; - let rc: c::long = 0; - res = curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &rc); - if (res != 0) { - return res: getinfoerr; - }; - fmt::println(rc)!; -}; - -export fn main() void = { - if (len(os::args) != 2) { - fmt::fatalf("USAGE: {} ", os::args[0]); - }; - match (get(os::args[1])) { - case void => - yield; - case let err: setopterr => - fmt::fatalf("setopt returned {} instead of 0", err: int); - case let err: performerr => - fmt::fatalf("perform returned {} instead of 0", err: int); - case let err: getinfoerr => - fmt::fatalf("getinfo returned {} instead of 0", err: int); - }; -}; diff --git a/internal/curl/README b/internal/curl/README new file mode 100644 index 0000000..8c56760 --- /dev/null +++ b/internal/curl/README @@ -0,0 +1 @@ +Basic hare bindings for libcurl's C API. Must be compiled with hare build $(pkg-config --libs-only-L --libs-only-l libcurl) ... to add the proper linker flags. diff --git a/internal/curl/ccurl/ccurl.ha b/internal/curl/ccurl/ccurl.ha new file mode 100644 index 0000000..3e44d97 --- /dev/null +++ b/internal/curl/ccurl/ccurl.ha @@ -0,0 +1,47 @@ +use types::c; + +export type CURL = opaque; +export type CURLcode = int; +export type CURLINFO = int; + +export def CURLOPT_URL = 10002; +export def CURLOPT_WRITEFUNCTION = 20011; +export def CURLOPT_WRITEDATA = 10001; + +export def CURLINFO_LONG = 0x200000; +export def CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2; + +export @symbol("curl_easy_init") fn curl_easy_init() nullable *CURL; +export @symbol("curl_easy_cleanup") fn curl_easy_cleanup(handle: nullable *CURL) void; + +export type write_callback = fn(ptr: *c::char, sz: c::ssize, nmemb: c::ssize, userdata: *opaque) c::ssize; +export @symbol("curl_easy_setopt") fn curl_easy_setopt( + handle: nullable *CURL, + option: int, + parameter: const *c::char, +) CURLcode; + +export @symbol("curl_easy_setopt") fn curl_easy_setopt_writefunction( + handle: nullable *CURL, + option: int, + parameter: *write_callback, +) CURLcode; + +export @symbol("curl_easy_setopt") fn curl_easy_setopt_writedata( + handle: nullable *CURL, + option: int, + parameter: *opaque, +) CURLcode; + +//export @symbol("curl_easy_setopt") fn curl_easy_setopt( +// handle: nullable *CURL, +// option: int, +// parameter: (const *c::char | nullable opaque) +//) CURLcode; + +export @symbol("curl_easy_perform") fn curl_easy_perform(easy_handle: nullable *CURL) CURLcode; +export @symbol("curl_easy_getinfo") fn curl_easy_getinfo( + easy_handle: nullable *CURL, + info: CURLINFO, + code: nullable *c::long +) CURLcode; diff --git a/internal/curl/curl.ha b/internal/curl/curl.ha new file mode 100644 index 0000000..148164a --- /dev/null +++ b/internal/curl/curl.ha @@ -0,0 +1,74 @@ +use types::c; +use fmt; +use os; +use memio; +use strings; +use io; +use internal::curl::ccurl; + +export type curlerror = !(setopterr | performerr | getinfoerr); + +export type setopterr = !int; +export type performerr = !int; +export type getinfoerr = !int; + +// A HTTP response +export type response = struct { + // Body + data: memio::stream, + // HTTP status + status: i64, + // Size of the body + sz: i64, +}; + +// Creates a response and returns it. Caller must call [[closeresponse]] to +// close the underlying stream. +export fn newresponse() response = { + return response { + data = memio::dynamic(), + status = 0, + sz = 0, + }; +}; + +// Closes the stream associated to the response created by [[newresponse]]. +export fn closeresponse(resp: response) (void | io::error) = { + io::close(&resp.data)?; +}; + +fn cb(ptr: *c::char, sz: c::ssize, nmemb: c::ssize, userdata: *opaque) c::ssize = { + let s = c::tostr(ptr)!; + //fmt::printfln("FOOKL: {}", s)!; + let resp = userdata: *response; + let realsize = nmemb * sz; + let b = strings::toutf8(s); + memio::concat(&resp.data, strings::fromutf8_unsafe(b[..realsize]))!; + resp.sz += sz * nmemb; + return sz * nmemb; +}; + +// Makes a HTTP request to the given url and fills resp with the body, HTTP +// status and body size. +export fn get(url: const str, resp: *response) (void | curlerror) = { + let c = ccurl::curl_easy_init(); + defer ccurl::curl_easy_cleanup(c); + let c_url = c::fromstr(url); + defer free(c_url); + let res = ccurl::curl_easy_setopt(c, ccurl::CURLOPT_URL, c_url); + if (res != 0) { + return res: setopterr; + }; + res = ccurl::curl_easy_setopt_writefunction(c, ccurl::CURLOPT_WRITEFUNCTION, &cb); + res = ccurl::curl_easy_setopt_writedata(c, ccurl::CURLOPT_WRITEDATA, resp); + res = ccurl::curl_easy_perform(c); + if (res != 0) { + return res: performerr; + }; + let rc: c::long = 0; + res = ccurl::curl_easy_getinfo(c, ccurl::CURLINFO_RESPONSE_CODE, &rc); + if (res != 0) { + return res: getinfoerr; + }; + resp.status = rc; +}; -- cgit v1.2.3