Skip to content

Commit

Permalink
Merge pull request #98 from treeform/guzba
Browse files Browse the repository at this point in the history
2.0.3, multipart + example
  • Loading branch information
treeform authored Jan 17, 2023
2 parents 0115db0 + 32ca635 commit d3dd4d9
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 75 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
22 changes: 22 additions & 0 deletions examples/multipart.nim
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions puppy.nimble
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
144 changes: 72 additions & 72 deletions src/puppy/platforms/linux/platform.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,77 +42,77 @@ proc fetch*(req: Request): Response {.raises: [PuppyError].} =
strings.add k & ": " & v

let curl = easy_init()
defer:
curl.easy_cleanup()
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
let
ret = curl.easy_perform()
headerData = headerWrap.str

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:
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

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))
raise newException(PuppyError, $easy_strerror(ret))
finally:
curl.easy_cleanup()

0 comments on commit d3dd4d9

Please sign in to comment.