diff options
author | bptato <nincsnevem662@gmail.com> | 2023-12-13 12:08:05 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-12-13 12:56:28 +0100 |
commit | ab203acf554993d15e37604773f160c84b4d8252 (patch) | |
tree | 45428aa45bc751f788cc5c52c32b15bb8a2363f1 /adapter/protocol/http.nim | |
parent | bf761bcb6dcc5288a86aa5e8c2b67df3f0df056b (diff) | |
download | chawan-ab203acf554993d15e37604773f160c84b4d8252.tar.gz |
Move http out of main binary
Now it is (technically) no longer mandatory to link to libcurl. Also, Chawan is at last completely protocol and network backend agnostic :) * Implement multipart requests in local CGI * Implement simultaneous download of CGI data * Add REQUEST_HEADERS env var with all headers * cssparser: add a missing check in consumeEscape
Diffstat (limited to 'adapter/protocol/http.nim')
-rw-r--r-- | adapter/protocol/http.nim | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/adapter/protocol/http.nim b/adapter/protocol/http.nim new file mode 100644 index 00000000..10b0c060 --- /dev/null +++ b/adapter/protocol/http.nim @@ -0,0 +1,128 @@ +import std/envvars +import std/options +import std/strutils + +import curlerrors +import curlwrap + +import bindings/curl +import types/opt +import utils/twtstr + +type + EarlyHintState = enum + NO_EARLY_HINT, EARLY_HINT_STARTED, EARLY_HINT_DONE + + HttpHandle = ref object + curl: CURL + statusline: bool + connectreport: bool + earlyhint: EarlyHintState + slist: curl_slist + +proc curlWriteHeader(p: cstring, size, nitems: csize_t, userdata: pointer): + csize_t {.cdecl.} = + var line = newString(nitems) + if nitems > 0: + prepareMutation(line) + copyMem(addr line[0], p, nitems) + + let op = cast[HttpHandle](userdata) + if not op.statusline: + op.statusline = true + var status: clong + op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) + if status == 103 and op.earlyhint == NO_EARLY_HINT: + op.earlyhint = EARLY_HINT_STARTED + else: + op.connectreport = true + stdout.write("Status: " & $status & "\n") + stdout.write("Cha-Control: ControlDone\n") + return nitems + + if line == "": + # empty line (last, before body) + if op.earlyhint == EARLY_HINT_STARTED: + # ignore; we do not have a way to stream headers yet. + op.earlyhint = EARLY_HINT_DONE + # reset statusline; we are awaiting the next line. + op.statusline = false + return nitems + return nitems + + if op.earlyhint != EARLY_HINT_STARTED: + # Regrettably, we can only write early hint headers after the status + # code is already known. + # For now, it seems easiest to just ignore them all. + stdout.write(line) + return nitems + +# From the documentation: size is always 1. +proc curlWriteBody(p: cstring, size, nmemb: csize_t, userdata: pointer): + csize_t {.cdecl.} = + return csize_t(stdout.writeBuffer(p, int(nmemb))) + +# From the documentation: size is always 1. +proc readFromStdin(buffer: cstring, size, nitems: csize_t, userdata: pointer): + csize_t {.cdecl.} = + return csize_t(stdin.readBuffer(buffer, nitems)) + +proc curlPreRequest(clientp: pointer, conn_primary_ip, conn_local_ip: cstring, + conn_primary_port, conn_local_port: cint): cint {.cdecl.} = + let op = cast[HttpHandle](clientp) + op.connectreport = true + stdout.write("Cha-Control: Connected\n") + return 0 # ok + +proc main() = + let curl = curl_easy_init() + doAssert curl != nil + let surl = getEnv("QUERY_STRING") + curl.setopt(CURLOPT_URL, surl) + let op = HttpHandle(curl: curl) + curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody) + curl.setopt(CURLOPT_HEADERDATA, op) + curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader) + curl.setopt(CURLOPT_PREREQDATA, op) + curl.setopt(CURLOPT_PREREQFUNCTION, curlPreRequest) + let proxy = getEnv("ALL_PROXY") + if proxy != "": + curl.setopt(CURLOPT_PROXY, proxy) + case getEnv("REQUEST_METHOD") + of "GET": + curl.setopt(CURLOPT_HTTPGET, 1) + of "POST": + curl.setopt(CURLOPT_POST, 1) + let len = parseInt64(getEnv("CONTENT_LENGTH")).get + # > For any given platform/compiler curl_off_t must be typedef'ed to + # a 64-bit + # > wide signed integral data type. The width of this data type must remain + # > constant and independent of any possible large file support settings. + # > + # > As an exception to the above, curl_off_t shall be typedef'ed to + # a 32-bit + # > wide signed integral data type if there is no 64-bit type. + # It seems safe to assume that if the platform has no uint64 then Nim won't + # compile either. In return, we are allowed to post >2G of data. + curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, uint64(len)) + curl.setopt(CURLOPT_READFUNCTION, readFromStdin) + else: discard #TODO + let headers = getEnv("REQUEST_HEADERS") + for line in headers.split("\r\n"): + if line.startsWithNoCase("Accept-Encoding: "): + let s = line.after(' ') + # From the CURLOPT_ACCEPT_ENCODING manpage: + # > The application does not have to keep the string around after + # > setting this option. + curl.setopt(CURLOPT_ACCEPT_ENCODING, cstring(s)) + # This is OK, because curl_slist_append strdup's line. + op.slist = curl_slist_append(op.slist, cstring(line)) + if op.slist != nil: + curl.setopt(CURLOPT_HTTPHEADER, op.slist) + let res = curl_easy_perform(curl) + if res != CURLE_OK and not op.connectreport: + stdout.write(getCurlConnectionError(res)) + op.connectreport = true + curl_easy_cleanup(curl) + +main() |