Skip to content

Commit

Permalink
Files (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
mishankov authored Aug 21, 2024
1 parent 433fe7d commit 72aa3ef
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 9 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, macOS-latest, windows-latest ]
nim-version: [ "2.0.0", "2.0.2", "2.0.4", "2.0.6", "2.0.8" ]
nim-version: [ "2.0.x" ]

steps:
- name: Checkout repository
Expand All @@ -45,15 +45,15 @@ jobs:
needs:
- build

if: github.ref_type == 'tag'

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

if: github.event_name == 'release' && github.event.action == 'created'

runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Arguments:
- `query` - request query params. Example: `{"param1": "val", "param2": "val2"}`
- `encodeQueryParams` - parameters for `encodeQuery` function that encodes query params. [More](https://nim-lang.org/docs/uri.html#encodeQuery%2CopenArray%5B%5D%2Cchar)
- `body` - request body as a string. Example: `"{\"key\": \"value\"}"`. Is not available for `get`, `head` and `options` procedures
- `files` - array of files to upload. Every file is a tuple of multipart name, file name, content type and content
- `sreamingFiles` - array of files to stream from disc and upload. Every file is a tuple of multipart name and file path
- `auth` - login and password for basic authorization. Example: `("login", "password")`
- `timeout` - stop waiting for a response after a given number of milliseconds. `-1` for no timeout, which is default value
- `ignoreSsl` - no certificate verification if `true`
Expand Down
3 changes: 3 additions & 0 deletions examples/examples.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ for catTag in catTags[0..4]:
echo "Response status: ", catData.status
echo "Response headers: ", catData.headers
echo "Response body: ", catData.body

# Send file
echo post("https://validator.w3.org/check", files = @[("uploaded_file", "test.html", "text/html", "<html><head></head><body><p>test</p></body></html>")]).body
32 changes: 29 additions & 3 deletions src/yahttp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type
omitEq*: bool
sep*: char

MultipartFile* = tuple[multipartName, fileName, contentType,
content: string] ## Type for uploaded file

StreamingMultipartFile* = tuple[name, file: string] ## Type for streaming file

Method* = enum
## Supported HTTP methods
GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS
Expand Down Expand Up @@ -85,8 +90,10 @@ const defaultEncodeQueryParams = EncodeQueryParams(usePlus: false, omitEq: true,
proc request*(url: string, httpMethod: Method = Method.GET, headers: openArray[
RequestHeader] = [], query: openArray[QueryParam] = [],
encodeQueryParams: EncodeQueryParams = defaultEncodeQueryParams,
body: string = "",
auth: BasicAuth = ("", ""), timeout = -1, ignoreSsl = false, sslContext: SslContext = nil): Response =
body: string = "", files: openArray[MultipartFile] = [],
streamingFiles: openArray[StreamingMultipartFile] = [],
auth: BasicAuth = ("", ""), timeout = -1, ignoreSsl = false,
sslContext: SslContext = nil): Response =
## Genreal proc to make HTTP request with every HTTP method

# Prepare client
Expand Down Expand Up @@ -131,7 +138,26 @@ proc request*(url: string, httpMethod: Method = Method.GET, headers: openArray[

# Make request

let response = client.request(innerUrl, httpMethod = innerMethod, body = body)
let response = if files.len() > 0:
# Prepare multipart data for files

var multipartData = newMultipartData()
for file in files:
multipartData[file.multipartName] = (file.fileName, file.contentType, file.content)
client.request(innerUrl, httpMethod = innerMethod,
multipart = multipartData)
elif streamingFiles.len() > 0:
# Prepare multipart data for streaming files

var multipartData = newMultipartData()
# for file in streamingFiles:
multipartData.addFiles(streamingFiles)
client.request(innerUrl, httpMethod = innerMethod,
multipart = multipartData)

else:
client.request(innerUrl, httpMethod = innerMethod, body = body)

client.close()

return response.toResp(requestUrl = innerUrl, requestHeaders = innerHeaders,
Expand Down
4 changes: 3 additions & 1 deletion src/yahttp/internal/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ macro http_method_gen*(name: untyped): untyped =
let comment = newCommentStmtNode(fmt"Proc for {methodUpper} HTTP method")
quote do:
proc `name`*(url: string, headers: openArray[RequestHeader] = [], query: openArray[
QueryParam] = [], encodeQueryParams: EncodeQueryParams = defaultEncodeQueryParams, body: string = "", auth: BasicAuth = ("", ""), timeout = -1,
QueryParam] = [], encodeQueryParams: EncodeQueryParams = defaultEncodeQueryParams, body: string = "", files: openArray[MultipartFile] = [], streamingFiles: openArray[StreamingMultipartFile] = [], auth: BasicAuth = ("", ""), timeout = -1,
ignoreSsl = false, sslContext: SslContext = nil): Response =
`comment`
return request(
Expand All @@ -15,6 +15,8 @@ macro http_method_gen*(name: untyped): untyped =
headers = headers,
query = query,
body = body,
files = files,
streamingFiles = streamingFiles,
auth = auth,
timeout = timeout,
ignoreSsl = ignoreSsl,
Expand Down
1 change: 1 addition & 0 deletions tests/int/test_data/test_file_1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test content
1 change: 1 addition & 0 deletions tests/int/test_data/test_file_2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test content 2
53 changes: 52 additions & 1 deletion tests/int/test_yahttp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ include yahttp


const BASE_URL = "http://localhost:8080"
const INT_TESTS_BASE_PATH = "tests/int"

test "Test HTTP methods":
check get(BASE_URL & "/get").ok()
check head(BASE_URL & "/head").ok()
check put(BASE_URL & "/put").ok()
check post(BASE_URL & "/post").ok()
check patch(BASE_URL & "/patch").ok()
Expand Down Expand Up @@ -39,7 +42,7 @@ test "Test JSON body":

check jsonResp["json"]["key"].getStr() == "value"

test "Test body with toJson helper":
test "Test body with toJsonString helper":
type TestReq = object
field1: string
field2: int
Expand All @@ -55,3 +58,51 @@ test "Test timeout":

# No exception
discard get(BASE_URL & "/delay/5", timeout = -1)


test "Test sending single file":
let resp = post(BASE_URL & "/post", files = @[("my_file", "test.txt", "text/plain", "some file content")]).json()

check resp["files"]["my_file"][0].getStr() == "some file content"
check resp["data"].getStr().contains("test.txt")
check resp["data"].getStr().contains("text/plain")
check resp["data"].getStr().contains("some file content")

test "Test sending multiple files":
let resp = post(BASE_URL & "/post", files = @[("my_file", "test.txt", "text/plain", "some file content"), ("my_second_file", "test2.txt", "text/plain", "second file content")]).json()

check resp["files"]["my_file"][0].getStr() == "some file content"
check resp["files"]["my_second_file"][0].getStr() == "second file content"
check resp["data"].getStr().contains("test.txt")
check resp["data"].getStr().contains("text/plain")
check resp["data"].getStr().contains("some file content")
check resp["data"].getStr().contains("test2.txt")
check resp["data"].getStr().contains("text/plain")
check resp["data"].getStr().contains("second file content")


const TEST_FILE_PATH_1 = INT_TESTS_BASE_PATH & "/test_data/test_file_1.txt"
const TEST_FILE_CONTENT_1 = readFile(TEST_FILE_PATH_1)

const TEST_FILE_PATH_2 = INT_TESTS_BASE_PATH & "/test_data/test_file_2.txt"
const TEST_FILE_CONTENT_2 = readFile(TEST_FILE_PATH_2)

test "Test streaming single file":
let resp = post(BASE_URL & "/post", streamingFiles = @[("my_file", TEST_FILE_PATH_1)]).json()

check resp["files"]["my_file"][0].getStr() == TEST_FILE_CONTENT_1
check resp["data"].getStr().contains("test_file_1.txt")
check resp["data"].getStr().contains("text/plain")
check resp["data"].getStr().contains(TEST_FILE_CONTENT_1)

test "Test streaming multiple files":
let resp = post(BASE_URL & "/post", streamingFiles = @[("my_file", TEST_FILE_PATH_1), ("my_second_file", TEST_FILE_PATH_2)]).json()

check resp["files"]["my_file"][0].getStr() == TEST_FILE_CONTENT_1
check resp["files"]["my_second_file"][0].getStr() == TEST_FILE_CONTENT_2
check resp["data"].getStr().contains("test_file_1.txt")
check resp["data"].getStr().contains("text/plain")
check resp["data"].getStr().contains(TEST_FILE_CONTENT_1)
check resp["data"].getStr().contains("test_file_2.txt")
check resp["data"].getStr().contains("text/plain")
check resp["data"].getStr().contains(TEST_FILE_CONTENT_2)
2 changes: 1 addition & 1 deletion yahttp.nimble
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package

version = "0.12.0"
version = "0.13.0"
author = "Denis Mishankov"
description = "Awesome simple HTTP client"
license = "MIT"
Expand Down

0 comments on commit 72aa3ef

Please sign in to comment.