Module httpclient

This module implements a simple HTTP client that can be used to retrieve webpages and other data.

Retrieving a website

This example uses HTTP GET to retrieve http://google.com:

var client = newHttpClient()
echo client.getContent("http://google.com")

The same action can also be performed asynchronously, simply use the AsyncHttpClient:

var client = newAsyncHttpClient()
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 shown here.

Note: You will need to run asynchronous examples in an async proc otherwise you will get an Undeclared identifier: 'await' error.

Using HTTP POST

This example demonstrates the usage of the W3 HTML Validator, it uses multipart/form-data as the Content-Type to send the HTML to be validated to the server.

var client = newHttpClient()
var data = newMultipartData()
data["output"] = "soap12"
data["uploaded_file"] = ("test.html", "text/html",
  "<html><head></head><body><p>test</p></body></html>")

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

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

You may specify a callback procedure to be called during an HTTP request. This callback will be executed every second with information about the progress of the HTTP request.

var client = newAsyncHttpClient()
proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} =
  echo("Downloaded ", progress, " of ", total)
  echo("Current rate: ", speed div 1000, "kb/s")
client.onProgressChanged = onProgressChanged
discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test")

If you would like to remove the callback simply set it to nil.

client.onProgressChanged = nil

Warning: The total reported by httpclient may be 0 in some cases.

SSL/TLS support

This requires the OpenSSL library, fortunately it's widely used and installed on many operating systems. httpclient will use SSL automatically if you give any of the functions a url with the https schema, for example: https://github.com/.

You will also have to compile with ssl defined like so: nim c -d:ssl ....

Timeouts

Currently only the synchronous functions support a timeout. The timeout is measured in milliseconds, once it is set any call on a socket which may block will be susceptible to this timeout.

It may be surprising but the function as a whole can take longer than the specified timeout, only individual internal calls on the socket are affected. In practice this means that as long as the server is sending data an exception will not be raised, if however data does not reach the client within the specified timeout a TimeoutError exception will be raised.

Proxy

A proxy can be specified as a param to any of the procedures defined in this module. To do this, use the newProxy constructor. Unfortunately, only basic authentication is supported at the moment.

Types

Response = ref object
  version*: string
  status*: string
  headers*: HttpHeaders
  body: string
  bodyStream*: Stream
  Source Edit
AsyncResponse = ref object
  version*: string
  status*: string
  headers*: HttpHeaders
  body: string
  bodyStream*: FutureStream[string]
  Source Edit
Proxy = ref object
  url*: Uri
  auth*: string
  Source Edit
MultipartEntries = openArray[tuple[name, content: string]]
  Source Edit
MultipartData = ref object
  content: seq[string]
  Source Edit
ProtocolError = object of IOError
exception that is raised when server does not conform to the implemented protocol   Source Edit
HttpRequestError = object of IOError
Thrown in the getContent proc and postContent proc, when the server returns an error   Source Edit
ProgressChangedProc[ReturnType] = proc (total, progress, speed: BiggestInt): ReturnType {.
closure, gcsafe
.}
  Source Edit
HttpClientBase[SocketType] = ref object
  socket: SocketType
  connected: bool
  currentURL: Uri
  headers*: HttpHeaders        ## Headers to send in requests.
  maxRedirects: int
  userAgent: string
  timeout: int                 ## Only used for blocking HttpClient for now.
  proxy: Proxy                 ## ``nil`` or the callback to call when request progress changes.
  when SocketType is Socket: onProgressChanged
  else: onProgressChanged
  when false: sslContext
  contentTotal: BiggestInt
  contentProgress: BiggestInt
  oneSecondProgress: BiggestInt
  lastProgressReport: float
  when SocketType is AsyncSocket: bodyStream
  else: bodyStream
  getBody: bool                ## When `false`, the body is never read in requestAux.
  
Where we are currently connected.   Source Edit
HttpClient = HttpClientBase[Socket]
  Source Edit
AsyncHttpClient = HttpClientBase[AsyncSocket]
  Source Edit

Consts

defUserAgent = "Nim httpclient/0.17.0"
  Source Edit

Procs

proc code(response: Response | AsyncResponse): HttpCode {.
raises: [ValueError, OverflowError]
.}

Retrieves the specified response's HttpCode.

Raises a ValueError if the response's status does not have a corresponding HttpCode.

  Source Edit
proc body(response: Response): string {.
raises: [Exception], tags: [ReadIOEffect]
.}

Retrieves the specified response's body.

The response's body stream is read synchronously.

  Source Edit
proc body=(response: Response; value: string) {.
deprecated, raises: [], tags: []
.}

Setter for backward compatibility.

This is deprecated and should not be used.

  Source Edit
proc body(response: AsyncResponse): Future[string] {.
raises: [FutureError], tags: [RootEffect]
.}
Reads the response's body and caches it. The read is performed only once.   Source Edit
proc newProxy(url: string; auth = ""): Proxy {.
raises: [ValueError], tags: []
.}
Constructs a new TProxy object.   Source Edit
proc newMultipartData(): MultipartData {.
raises: [], tags: []
.}
Constructs a new MultipartData object.   Source Edit
proc add(p: var MultipartData; name, content: string; filename: string = nil;
        contentType: string = nil) {.
raises: [ValueError], tags: []
.}
Add a value to the multipart data. Raises a ValueError exception if name, filename or contentType contain newline characters.   Source Edit
proc add(p: var MultipartData; xs: MultipartEntries): MultipartData {.
discardable, raises: [ValueError], tags: []
.}
Add a list of multipart entries to the multipart data p. All values are added without a filename and without a content type.
data.add({"action": "login", "format": "json"})
  Source Edit
proc newMultipartData(xs: MultipartEntries): MultipartData {.
raises: [ValueError], tags: []
.}
Create a new multipart data object and fill it with the entries xs directly.
var data = newMultipartData({"action": "login", "format": "json"})
  Source Edit
proc addFiles(p: var MultipartData; xs: openArray[tuple[name, file: string]]): MultipartData {.
discardable, raises: [ValueError, IOError], tags: [ReadIOEffect]
.}
Add files to a multipart data object. The file will be opened from your disk, read and sent with the automatically determined MIME type. Raises an IOError if the file cannot be opened or reading fails. To manually specify file content, filename and MIME type, use []= instead.
data.addFiles({"uploaded_file": "public/test.html"})
  Source Edit
proc `[]=`(p: var MultipartData; name, content: string) {.
raises: [ValueError], tags: []
.}
Add a multipart entry to the multipart data p. The value is added without a filename and without a content type.
data["username"] = "NimUser"
  Source Edit
proc `[]=`(p: var MultipartData; name: string;
          file: tuple[name, contentType, content: string]) {.
raises: [ValueError], tags: []
.}
Add a file to the multipart data p, specifying filename, contentType and content manually.
data["uploaded_file"] = ("test.html", "text/html",
  "<html><head></head><body><p>test</p></body></html>")
  Source Edit
proc request(url: string; httpMethod: string; extraHeaders = ""; body = "";
            sslContext = defaultSSLContext; timeout = - 1; userAgent = defUserAgent;
            proxy: Proxy = nil): Response {.
deprecated, raises: [ValueError, OSError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError, ValueError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Requests url with the custom method string specified by the
httpMethod parameter.
Extra headers can be specified and must be separated by \c\L
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised.

Deprecated since version 0.15.0: use HttpClient.request instead.

  Source Edit
proc request(url: string; httpMethod = httpGET; extraHeaders = ""; body = "";
            sslContext = defaultSSLContext; timeout = - 1; userAgent = defUserAgent;
            proxy: Proxy = nil): Response {.
deprecated, raises: [ValueError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Requests url with the specified httpMethod.
Extra headers can be specified and must be separated by \c\L
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised.

Deprecated since version 0.15.0: use HttpClient.request instead.

  Source Edit
proc get(url: string; extraHeaders = ""; maxRedirects = 5;
        sslContext: SSLContext = defaultSSLContext; timeout = - 1;
        userAgent = defUserAgent; proxy: Proxy = nil): Response {.
deprecated, raises: [ ValueError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

GETs the url and returns a Response object
This proc also handles redirection
Extra headers can be specified and must be separated by \c\L.
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised.

## Deprecated since version 0.15.0: use HttpClient.get instead.

  Source Edit
proc getContent(url: string; extraHeaders = ""; maxRedirects = 5;
               sslContext: SSLContext = defaultSSLContext; timeout = - 1;
               userAgent = defUserAgent; proxy: Proxy = nil): string {.
deprecated, raises: [ ValueError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

GETs the body and returns it as a string.
Raises exceptions for the status codes 4xx and 5xx
Extra headers can be specified and must be separated by \c\L.
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised.

Deprecated since version 0.15.0: use HttpClient.getContent instead.

  Source Edit
proc post(url: string; extraHeaders = ""; body = ""; maxRedirects = 5;
         sslContext: SSLContext = defaultSSLContext; timeout = - 1;
         userAgent = defUserAgent; proxy: Proxy = nil; multipart: MultipartData = nil): Response {.
deprecated, raises: [ValueError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

POSTs body to the url and returns a Response object.
This proc adds the necessary Content-Length header.
This proc also handles redirection.
Extra headers can be specified and must be separated by \c\L.
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised. | The optional multipart parameter can be used to create multipart/form-data POSTs comfortably.

Deprecated since version 0.15.0: use HttpClient.post instead.

  Source Edit
proc postContent(url: string; extraHeaders = ""; body = ""; maxRedirects = 5;
                sslContext: SSLContext = defaultSSLContext; timeout = - 1;
                userAgent = defUserAgent; proxy: Proxy = nil;
                multipart: MultipartData = nil): string {.
deprecated, raises: [ ValueError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

POSTs body to url and returns the response's body as a string
Raises exceptions for the status codes 4xx and 5xx
Extra headers can be specified and must be separated by \c\L.
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised. | The optional multipart parameter can be used to create multipart/form-data POSTs comfortably.

Deprecated since version 0.15.0: use HttpClient.postContent instead.

  Source Edit
proc downloadFile(url: string; outputFilename: string;
                 sslContext: SSLContext = defaultSSLContext; timeout = - 1;
                 userAgent = defUserAgent; proxy: Proxy = nil) {.
deprecated, raises: [ IOError, ValueError, OSError, HttpRequestError, SslError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [WriteIOEffect, ReadIOEffect, TimeEffect]
.}

Downloads url and saves it to outputFilename
An optional timeout can be specified in milliseconds, if reading from the

server takes longer than specified an ETimeout exception will be raised.

Deprecated since version 0.16.2: use HttpClient.downloadFile instead.

  Source Edit
proc newHttpClient(userAgent = defUserAgent; maxRedirects = 5;
                  sslContext = defaultSslContext; proxy: Proxy = nil; timeout = - 1): HttpClient {.
raises: [], tags: []
.}

Creates a new HttpClient instance.

userAgent specifies the user agent that will be used when making requests.

maxRedirects specifies the maximum amount of redirects to follow, default is 5.

sslContext specifies the SSL context to use for HTTPS requests.

proxy specifies an HTTP proxy to use for this HTTP client's connections.

timeout specifies the number of milliseconds to allow before a TimeoutError is raised.

  Source Edit
proc newAsyncHttpClient(userAgent = defUserAgent; maxRedirects = 5;
                       sslContext = defaultSslContext; proxy: Proxy = nil): AsyncHttpClient {.
raises: [], tags: []
.}

Creates a new AsyncHttpClient instance.

userAgent specifies the user agent that will be used when making requests.

maxRedirects specifies the maximum amount of redirects to follow, default is 5.

sslContext specifies the SSL context to use for HTTPS requests.

proxy specifies an HTTP proxy to use for this HTTP client's connections.

  Source Edit
proc close(client: HttpClient | AsyncHttpClient)
Closes any connections held by the HTTP client.   Source Edit
proc request(client: AsyncHttpClient; url: string; httpMethod: string; body = "";
            headers: HttpHeaders = nil): Future[AsyncResponse] {.
raises: [FutureError], tags: [RootEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a request using the custom method string specified by httpMethod.

Connection will kept alive. Further requests on the same client to the same hostname will not require a new connection to be made. The connection can be closed by using the close procedure.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

  Source Edit
proc request(client: HttpClient; url: string; httpMethod: string; body = "";
            headers: HttpHeaders = nil): Response {.
raises: [ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a request using the custom method string specified by httpMethod.

Connection will kept alive. Further requests on the same client to the same hostname will not require a new connection to be made. The connection can be closed by using the close procedure.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

  Source Edit
proc request(client: AsyncHttpClient; url: string; httpMethod = HttpGET; body = "";
            headers: HttpHeaders = nil): Future[AsyncResponse] {.
raises: [FutureError], tags: [RootEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a request using the method specified.

Connection will be kept alive. Further requests on the same client to the same hostname will not require a new connection to be made. The connection can be closed by using the close procedure.

When a request is made to a different hostname, the current connection will be closed.

  Source Edit
proc request(client: HttpClient; url: string; httpMethod = HttpGET; body = "";
            headers: HttpHeaders = nil): Response {.
raises: [ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a request using the method specified.

Connection will be kept alive. Further requests on the same client to the same hostname will not require a new connection to be made. The connection can be closed by using the close procedure.

When a request is made to a different hostname, the current connection will be closed.

  Source Edit
proc get(client: AsyncHttpClient; url: string): Future[AsyncResponse] {.
raises: [FutureError], tags: [RootEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a GET request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

  Source Edit
proc get(client: HttpClient; url: string): Response {.
raises: [ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a GET request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

  Source Edit
proc getContent(client: AsyncHttpClient; url: string): Future[string] {.
raises: [FutureError], tags: [RootEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a GET request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

A HttpRequestError will be raised if the server responds with a client error (status code 4xx) or a server error (status code 5xx).

  Source Edit
proc getContent(client: HttpClient; url: string): string {.
raises: [ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a GET request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

A HttpRequestError will be raised if the server responds with a client error (status code 4xx) or a server error (status code 5xx).

  Source Edit
proc post(client: AsyncHttpClient; url: string; body = "";
         multipart: MultipartData = nil): Future[AsyncResponse] {.
raises: [FutureError], tags: [RootEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a POST request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

  Source Edit
proc post(client: HttpClient; url: string; body = ""; multipart: MultipartData = nil): Response {.
raises: [ ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a POST request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

  Source Edit
proc postContent(client: AsyncHttpClient; url: string; body = "";
                multipart: MultipartData = nil): Future[string] {.
raises: [FutureError], tags: [RootEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a POST request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

A HttpRequestError will be raised if the server responds with a client error (status code 4xx) or a server error (status code 5xx).

  Source Edit
proc postContent(client: HttpClient; url: string; body = "";
                multipart: MultipartData = nil): string {.
raises: [ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}

Connects to the hostname specified by the URL and performs a POST request.

This procedure will follow redirects up to a maximum number of redirects specified in client.maxRedirects.

A HttpRequestError will be raised if the server responds with a client error (status code 4xx) or a server error (status code 5xx).

  Source Edit
proc downloadFile(client: AsyncHttpClient; url: string; filename: string): Future[void] {.
raises: [FutureError], tags: [RootEffect, TimeEffect, ReadDirEffect]
.}
Downloads url and saves it to filename.   Source Edit
proc downloadFile(client: HttpClient; url: string; filename: string): void {.
raises: [ ValueError, HttpRequestError, SslError, OSError, IOError, TimeoutError, ProtocolError, KeyError, Exception, OverflowError], tags: [ReadIOEffect, WriteIOEffect, TimeEffect]
.}
Downloads url and saves it to filename.   Source Edit