summary refs log tree commit diff stats
path: root/lib/pure/httpclient.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/httpclient.nim')
-rw-r--r--lib/pure/httpclient.nim74
1 files changed, 55 insertions, 19 deletions
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 4404a9426..ba967b14f 100644
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -18,14 +18,14 @@
 ##
 ## .. code-block:: Nim
 ##   var client = newHttpClient()
-##   echo(getContent("http://google.com"))
+##   echo client.getContent("http://google.com")
 ##
 ## The same action can also be performed asynchronously, simply use the
 ## ``AsyncHttpClient``:
 ##
 ## .. code-block:: Nim
 ##   var client = newAsyncHttpClient()
-##   echo(await getContent("http://google.com"))
+##   echo await client.getContent("http://google.com")
 ##
 ## The functionality implemented by ``HttpClient`` and ``AsyncHttpClient``
 ## is the same, so you can use whichever one suits you best in the examples
@@ -50,6 +50,20 @@
 ##
 ##   echo client.postContent("http://validator.w3.org/check", multipart=data)
 ##
+## You can also make post requests with custom headers. 
+## This example sets ``Content-Type`` to ``application/json``
+## and uses a json object for the body
+##
+## .. code-block:: Nim
+##   import httpclient, json
+##   
+##   let client = newHttpClient()
+##   client.headers = newHttpHeaders({ "Content-Type": "application/json" })
+##   let body = %*{
+##       "data": "some text"
+##   }
+##   echo client.request("http://some.api", httpMethod = HttpPost, body = $body)
+##
 ## Progress reporting
 ## ==================
 ##
@@ -103,7 +117,7 @@
 ## only basic authentication is supported at the moment.
 
 import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes,
-  math, random, httpcore, times
+  math, random, httpcore, times, tables
 import asyncnet, asyncdispatch
 import nativesockets
 
@@ -432,7 +446,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
 
 
   # get the socket ready. If we are connecting through a proxy to SSL,
-  # send the appropiate CONNECT header. If not, simply connect to the proper
+  # send the appropriate CONNECT header. If not, simply connect to the proper
   # host (which may still be the proxy, for normal HTTP)
   if proxy != nil and hostUrl.scheme == "https":
     when defined(ssl):
@@ -736,7 +750,7 @@ proc newHttpClient*(userAgent = defUserAgent,
   ## ``proxy`` specifies an HTTP proxy to use for this HTTP client's
   ## connections.
   ##
-  ## ``timeout`` specifies the number of miliseconds to allow before a
+  ## ``timeout`` specifies the number of milliseconds to allow before a
   ## ``TimeoutError`` is raised.
   new result
   result.headers = newHttpHeaders()
@@ -944,8 +958,10 @@ proc parseResponse(client: HttpClient | AsyncHttpClient,
 proc newConnection(client: HttpClient | AsyncHttpClient,
                    url: Uri) {.multisync.} =
   if client.currentURL.hostname != url.hostname or
-      client.currentURL.scheme != url.scheme:
-    if client.connected: client.close()
+      client.currentURL.scheme != url.scheme or
+      client.currentURL.port != url.port:
+    if client.connected:
+      client.close()
 
     when client is HttpClient:
       client.socket = newSocket()
@@ -973,8 +989,20 @@ proc newConnection(client: HttpClient | AsyncHttpClient,
     client.currentURL = url
     client.connected = true
 
+proc override(fallback, override: HttpHeaders): HttpHeaders =
+  # Right-biased map union for `HttpHeaders`
+  if override.isNil:
+    return fallback
+
+  result = newHttpHeaders()
+  # Copy by value
+  result.table[] = fallback.table[]
+  for k, vs in override.table:
+    result[k] = vs
+
 proc request*(client: HttpClient | AsyncHttpClient, url: string,
-              httpMethod: string, body = ""): Future[Response] {.multisync.} =
+              httpMethod: string, body = "",
+              headers: HttpHeaders = nil): Future[Response] {.multisync.} =
   ## Connects to the hostname specified by the URL and performs a request
   ## using the custom method string specified by ``httpMethod``.
   ##
@@ -1007,13 +1035,15 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
   else:
     await newConnection(client, connectionUrl)
 
-  if not client.headers.hasKey("user-agent") and client.userAgent != "":
-    client.headers["User-Agent"] = client.userAgent
+  let effectiveHeaders = client.headers.override(headers)
+
+  if not effectiveHeaders.hasKey("user-agent") and client.userAgent != "":
+    effectiveHeaders["User-Agent"] = client.userAgent
 
-  var headers = generateHeaders(requestUrl, httpMethod,
-                                client.headers, body, client.proxy)
+  var headersString = generateHeaders(requestUrl, httpMethod,
+                                      effectiveHeaders, body, client.proxy)
 
-  await client.socket.send(headers)
+  await client.socket.send(headersString)
   if body != "":
     await client.socket.send(body)
 
@@ -1024,7 +1054,8 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
   client.proxy = savedProxy
 
 proc request*(client: HttpClient | AsyncHttpClient, url: string,
-              httpMethod = HttpGET, body = ""): Future[Response] {.multisync.} =
+              httpMethod = HttpGET, body = "",
+              headers: HttpHeaders = nil): Future[Response] {.multisync.} =
   ## Connects to the hostname specified by the URL and performs a request
   ## using the method specified.
   ##
@@ -1034,7 +1065,8 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
   ##
   ## When a request is made to a different hostname, the current connection will
   ## be closed.
-  result = await request(client, url, $httpMethod, body)
+  result = await request(client, url, $httpMethod, body,
+                         headers = headers)
 
 proc get*(client: HttpClient | AsyncHttpClient,
           url: string): Future[Response] {.multisync.} =
@@ -1081,18 +1113,22 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "",
     else:
       x
   var xb = mpBody.withNewLine() & body
+
+  var headers = newHttpHeaders()
   if multipart != nil:
-    client.headers["Content-Type"] = mpHeader.split(": ")[1]
-  client.headers["Content-Length"] = $len(xb)
+    headers["Content-Type"] = mpHeader.split(": ")[1]
+  headers["Content-Length"] = $len(xb)
 
-  result = await client.request(url, HttpPOST, xb)
+  result = await client.request(url, HttpPOST, xb,
+                                headers = headers)
   # Handle redirects.
   var lastURL = url
   for i in 1..client.maxRedirects:
     if result.status.redirection():
       let redirectTo = getNewLocation(lastURL, result.headers)
       var meth = if result.status != "307": HttpGet else: HttpPost
-      result = await client.request(redirectTo, meth, xb)
+      result = await client.request(redirectTo, meth, xb,
+                                    headers = headers)
       lastURL = redirectTo
 
 proc postContent*(client: HttpClient | AsyncHttpClient, url: string,