when NimMajor >= 2: import std/envvars else: import std/os import std/posix import std/strutils import curl import curlerrors import curlwrap 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 puts(s: string) = discard write(1, unsafeAddr s[0], s.len) 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 puts("Status: " & $status & "\nCha-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. puts(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(write(stdout.getFileHandle(), 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 puts("Cha-Control: Connected\n") return 0 # ok proc main() = let curl = curl_easy_init() doAssert curl != nil let url = curl_url() const flags = cuint(CURLU_PATH_AS_IS) url.set(CURLUPART_SCHEME, getEnv("MAPPED_URI_SCHEME"), flags) let username = getEnv("MAPPED_URI_USERNAME") if username != "": url.set(CURLUPART_USER, username, flags) let password = getEnv("MAPPED_URI_PASSWORD") if password != "": url.set(CURLUPART_PASSWORD, password, flags) url.set(CURLUPART_HOST, getEnv("MAPPED_URI_HOST"), flags) let port = getEnv("MAPPED_URI_PORT") if port != "": url.set(CURLUPART_PORT, port, flags) let path = getEnv("MAPPED_URI_PATH") if path != "": url.set(CURLUPART_PATH, path, flags) let query = getEnv("MAPPED_URI_QUERY") if query != "": url.set(CURLUPART_QUERY, query, flags) curl.setopt(CURLOPT_CURLU, url) 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 = parseInt(getEnv("CONTENT_LENGTH")) # > 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: puts(getCurlConnectionError(res)) op.connectreport = true curl_easy_cleanup(curl) main()