From 61074d09beca944eb55523ef8e1f2879ec792ea7 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 18:20:48 -0600 Subject: [PATCH 1/8] rm defer --- src/puppy/platforms/linux/platform.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/puppy/platforms/linux/platform.nim b/src/puppy/platforms/linux/platform.nim index c7dbe5a..066f90b 100644 --- a/src/puppy/platforms/linux/platform.nim +++ b/src/puppy/platforms/linux/platform.nim @@ -42,8 +42,6 @@ proc fetch*(req: Request): Response {.raises: [PuppyError].} = strings.add k & ": " & v let curl = easy_init() - defer: - curl.easy_cleanup() discard curl.easy_setopt(OPT_URL, strings[0].cstring) discard curl.easy_setopt(OPT_CUSTOMREQUEST, strings[1].cstring) @@ -94,6 +92,8 @@ proc fetch*(req: Request): Response {.raises: [PuppyError].} = ret = curl.easy_perform() headerData = headerWrap.str + curl.easy_cleanup() + if ret == E_OK: var httpCode: uint32 discard curl.easy_getinfo(INFO_RESPONSE_CODE, httpCode.addr) @@ -107,7 +107,7 @@ proc fetch*(req: Request): Response {.raises: [PuppyError].} = let arr = headerLine.split(":", 1) if arr.len == 2: result.headers.add((arr[0].strip(), arr[1].strip())) - + result.body = bodyWrap.str if result.headers["Content-Encoding"] == "gzip": try: From 9f81ed6f7072e38c7f0f6dee0bfe7065bbb4b9d3 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 18:21:53 -0600 Subject: [PATCH 2/8] add macos --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4395085..d409520 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] nim-version: ['1.2.2', '1.2.x', '1.4.x', 'stable'] include: - nim-version: '1.4.x' From 968561008d49587d34f0abf84fdf3653c55d92c3 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 18:22:24 -0600 Subject: [PATCH 3/8] 2.0.3 --- puppy.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/puppy.nimble b/puppy.nimble index a40ef79..641c189 100644 --- a/puppy.nimble +++ b/puppy.nimble @@ -1,4 +1,4 @@ -version = "2.0.2" +version = "2.0.3" author = "Andre von Houck" description = "Puppy fetches resources via HTTP and HTTPS." license = "MIT" From e46fc6e3813bd42dc031f2d25b36abd60133b5ec Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 18:26:35 -0600 Subject: [PATCH 4/8] add multipart example --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 02010a4..df8e516 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,33 @@ echo response.headers echo response.body.len ``` +## Examples + +Using multipart/form-data: + +```nim +var entries: seq[MultipartEntry] +entries.add MultipartEntry( + name: "input_text", + fileName: "input.txt", + contentType: "text/plain", + payload: "foobar" +) +entries.add MultipartEntry( + name: "options", + payload: "{\"utf8\":true}" +) + +let (contentType, body) = encodeMultipart(entries) + +var headers: HttpHeaders +headers["Content-Type"] = contentType + +let response = post("Your API endpoint here", headers, body) +``` + +See the [examples/](https://github.com/treeform/puppy) folder for more examples. + ## Always use Libcurl You can pass `-d:puppyLibcurl` to force use of `libcurl` even on windows and macOS. This is useful to debug, if the some reason native OS API does not work. Libcurl is usually installed on macOS but requires a `curl.dll` on windows. From bd42234d7ce5ebff6d62de244a2ad455b500654c Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 18:28:00 -0600 Subject: [PATCH 5/8] add examples dir --- examples/multipart.nim | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/multipart.nim diff --git a/examples/multipart.nim b/examples/multipart.nim new file mode 100644 index 0000000..67483bd --- /dev/null +++ b/examples/multipart.nim @@ -0,0 +1,22 @@ +import puppy + +var entries: seq[MultipartEntry] +entries.add MultipartEntry( + name: "input_text", + fileName: "input.txt", + contentType: "text/plain", + payload: "foobar" +) +entries.add MultipartEntry( + name: "options", + payload: "{\"utf8\":true}" +) + +let (contentType, body) = encodeMultipart(entries) + +var headers: HttpHeaders +headers["Content-Type"] = contentType + +let response = post("http://localhost:8080", headers, body) + +echo response.code From 86686321dc6bde6da90e52defcc9c0e0aa348ca7 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 19:22:51 -0600 Subject: [PATCH 6/8] update --- .github/workflows/build.yml | 1 + puppy.nimble | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d409520..477beb4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,5 +30,6 @@ jobs: - run: nimble test -y --gc:refc - run: nimble test -y --gc:arc - run: nimble test -y --gc:orc + - run: nimble test -d:release -y --gc:orc - run: nimble test -y --gc:arc --threads:on diff --git a/puppy.nimble b/puppy.nimble index 641c189..5f2a0b1 100644 --- a/puppy.nimble +++ b/puppy.nimble @@ -8,4 +8,4 @@ srcDir = "src" requires "nim >= 1.2.2" requires "libcurl >= 1.0.0" requires "zippy >= 0.10.0" -requires "webby >= 0.1.3" +requires "webby >= 0.1.6" From d384838fc51e66bf8905db50b70953cd04b212c9 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 19:58:56 -0600 Subject: [PATCH 7/8] try --- src/puppy/platforms/linux/platform.nim | 49 +++++++++++++------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/puppy/platforms/linux/platform.nim b/src/puppy/platforms/linux/platform.nim index 066f90b..30ac3c1 100644 --- a/src/puppy/platforms/linux/platform.nim +++ b/src/puppy/platforms/linux/platform.nim @@ -92,27 +92,28 @@ proc fetch*(req: Request): Response {.raises: [PuppyError].} = ret = curl.easy_perform() headerData = headerWrap.str - curl.easy_cleanup() - - if ret == E_OK: - var httpCode: uint32 - discard curl.easy_getinfo(INFO_RESPONSE_CODE, httpCode.addr) - result.code = httpCode.int - - var responseUrl: cstring - discard curl.easy_getinfo(INFO_EFFECTIVE_URL, responseUrl.addr) - result.url = $responseUrl - - for headerLine in headerData.split(CRLF): - let arr = headerLine.split(":", 1) - if arr.len == 2: - result.headers.add((arr[0].strip(), arr[1].strip())) - - result.body = bodyWrap.str - if result.headers["Content-Encoding"] == "gzip": - try: - result.body = uncompress(result.body, dfGzip) - except ZippyError as e: - raise newException(PuppyError, "Error uncompressing response", e) - else: - raise newException(PuppyError, $easy_strerror(ret)) + try: + if ret == E_OK: + var httpCode: uint32 + discard curl.easy_getinfo(INFO_RESPONSE_CODE, httpCode.addr) + result.code = httpCode.int + + var responseUrl: cstring + discard curl.easy_getinfo(INFO_EFFECTIVE_URL, responseUrl.addr) + result.url = $responseUrl + + for headerLine in headerData.split(CRLF): + let arr = headerLine.split(":", 1) + if arr.len == 2: + result.headers.add((arr[0].strip(), arr[1].strip())) + + result.body = bodyWrap.str + if result.headers["Content-Encoding"] == "gzip": + try: + result.body = uncompress(result.body, dfGzip) + except ZippyError as e: + raise newException(PuppyError, "Error uncompressing response", e) + else: + raise newException(PuppyError, $easy_strerror(ret)) + finally: + curl.easy_cleanup() From 32ca6355d1be620ab320e5d488ee654e89aa7a01 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Mon, 16 Jan 2023 20:39:58 -0600 Subject: [PATCH 8/8] f --- src/puppy/platforms/linux/platform.nim | 97 +++++++++++++------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/src/puppy/platforms/linux/platform.nim b/src/puppy/platforms/linux/platform.nim index 30ac3c1..ea16abc 100644 --- a/src/puppy/platforms/linux/platform.nim +++ b/src/puppy/platforms/linux/platform.nim @@ -42,57 +42,56 @@ proc fetch*(req: Request): Response {.raises: [PuppyError].} = strings.add k & ": " & v let curl = easy_init() + try: + discard curl.easy_setopt(OPT_URL, strings[0].cstring) + discard curl.easy_setopt(OPT_CUSTOMREQUEST, strings[1].cstring) + discard curl.easy_setopt(OPT_TIMEOUT, req.timeout.int) + + # Create the Pslist for passing headers to curl manually. This is to + # avoid needing to call slist_free_all which creates problems + var slists: seq[Slist] + for i, header in req.headers: + slists.add Slist(data: strings[2 + i].cstring, next: nil) + # Do this in two passes so the slists index addresses are stable + var headerList: Pslist + for i, header in req.headers: + if i == 0: + headerList = slists[0].addr + else: + var tail = headerList + while tail.next != nil: + tail = tail.next + tail.next = slists[i].addr + + discard curl.easy_setopt(OPT_HTTPHEADER, headerList) + + if req.verb.toUpperAscii() == "POST" or req.body.len > 0: + discard curl.easy_setopt(OPT_POSTFIELDSIZE, req.body.len) + discard curl.easy_setopt(OPT_POSTFIELDS, req.body.cstring) + + # Setup writers. + var headerWrap, bodyWrap: StringWrap + discard curl.easy_setopt(OPT_WRITEDATA, bodyWrap.addr) + discard curl.easy_setopt(OPT_WRITEFUNCTION, curlWriteFn) + discard curl.easy_setopt(OPT_HEADERDATA, headerWrap.addr) + discard curl.easy_setopt(OPT_HEADERFUNCTION, curlWriteFn) + + # On Windows look for cacert.pem. + when defined(windows): + discard curl.easy_setopt(OPT_CAINFO, "cacert.pem".cstring) + + # Follow up to 10 redirects by default. + discard curl.easy_setopt(OPT_FOLLOWLOCATION, 1) + discard curl.easy_setopt(OPT_MAXREDIRS, 10) + + if req.allowAnyHttpsCertificate: + discard curl.easy_setopt(OPT_SSL_VERIFYPEER, 0) + discard curl.easy_setopt(OPT_SSL_VERIFYHOST, 0) - discard curl.easy_setopt(OPT_URL, strings[0].cstring) - discard curl.easy_setopt(OPT_CUSTOMREQUEST, strings[1].cstring) - discard curl.easy_setopt(OPT_TIMEOUT, req.timeout.int) - - # Create the Pslist for passing headers to curl manually. This is to - # avoid needing to call slist_free_all which creates problems - var slists: seq[Slist] - for i, header in req.headers: - slists.add Slist(data: strings[2 + i].cstring, next: nil) - # Do this in two passes so the slists index addresses are stable - var headerList: Pslist - for i, header in req.headers: - if i == 0: - headerList = slists[0].addr - else: - var tail = headerList - while tail.next != nil: - tail = tail.next - tail.next = slists[i].addr - - discard curl.easy_setopt(OPT_HTTPHEADER, headerList) - - if req.verb.toUpperAscii() == "POST" or req.body.len > 0: - discard curl.easy_setopt(OPT_POSTFIELDSIZE, req.body.len) - discard curl.easy_setopt(OPT_POSTFIELDS, req.body.cstring) - - # Setup writers. - var headerWrap, bodyWrap: StringWrap - discard curl.easy_setopt(OPT_WRITEDATA, bodyWrap.addr) - discard curl.easy_setopt(OPT_WRITEFUNCTION, curlWriteFn) - discard curl.easy_setopt(OPT_HEADERDATA, headerWrap.addr) - discard curl.easy_setopt(OPT_HEADERFUNCTION, curlWriteFn) - - # On Windows look for cacert.pem. - when defined(windows): - discard curl.easy_setopt(OPT_CAINFO, "cacert.pem".cstring) - - # Follow up to 10 redirects by default. - discard curl.easy_setopt(OPT_FOLLOWLOCATION, 1) - discard curl.easy_setopt(OPT_MAXREDIRS, 10) - - if req.allowAnyHttpsCertificate: - discard curl.easy_setopt(OPT_SSL_VERIFYPEER, 0) - discard curl.easy_setopt(OPT_SSL_VERIFYHOST, 0) - - let - ret = curl.easy_perform() - headerData = headerWrap.str + let + ret = curl.easy_perform() + headerData = headerWrap.str - try: if ret == E_OK: var httpCode: uint32 discard curl.easy_getinfo(INFO_RESPONSE_CODE, httpCode.addr)