Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.0.3, multipart + example #98

Merged
merged 8 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()