HttpClient

A module for communicating over HTTP.

You start by building a RequestConfiguration type, which represents the request you'll make to a server. Once done, you can either do a send, which represents the response as a Task, or stream which will allow you to perform actions while the request is sending and while the response is coming in. A typical example of why you'd use stream is to show a progress bar to the user, or decode the response incrementally as opposed to all at once.

Initialization

Code that wishes to perform HTTP requests require a permission to do so.

type Permission

A value that represents the permission to perform HTTP requests.

Only code you trust should be granted permission.

initialize : Task Permission

Call this during application initialization to get the permission to perform any kind of HTTP request.

initializeForHost : String -> Task Permission

Call during application initialization to get a host-specific permission. Code that has this permission value, will only be able to contact a specific host.

Request configuration

In order to send something over HTTP, you first need a description of how that request will look like.

type alias RequestConfiguration responseType =
{ method : Method
, url : String
, headers : Dict String (Array String)
, body : Body
, expect : Expect responseType
, timeout : Int
}

Describes the request to be made. Use get, post or request to initialize this value, then customize it using the following with functions.

get : String -> RequestConfiguration {}

Initializes the configuration for a simple GET request to the given url.

post : String -> RequestConfiguration {}

Initializes the configuration for a simple POST request to the given url.

request : Method -> String -> RequestConfiguration {}

Initializes a request configuration with the given method and url.

Timeouts

A timeout represents how long you're willing to wait before giving up on receiving a response from the server. Servers might not respond for any number of reasons, like bugs or huge amounts of traffic, so it is a good idea to return an error to the user instead of waiting "forever" for a response.

defaultTimeout : Int

This is the default timeout value. It is set to 10 seconds. If you don't use withTimeout to set a timeout specifically, this value will be used.

withTimeout : Int -> RequestConfiguration a -> RequestConfiguration a

Lets you specify a timeout, in milliseconds, for a request. If the server doesn't respond to your request within the given timeout, the request will fail with a Timeout Error.

Headers

Every HTTP request can have arbitrary metadata attached, called headers. Headers allow you to attach things like authorization information, how the body is encoded or the name of the client making the request.

It might be interesting to read this list of HTTP header fields.

withHeader :
String
-> String
-> RequestConfiguration a
-> RequestConfiguration a

A header is a key-value pair of strings that says something about the request. Examples include the length of the body, authentication information, name of the client making the request, etc.

withDuplicatedHeader :
String
-> String
-> RequestConfiguration a
-> RequestConfiguration a

Header keys doesn't have to be unique. You're allowed to send the same kind of header multiple times, like sending multiple cookies. The behaviour of withHeader will replace the value of an already set header. This function will not.

Request body

The request body is the actual data that you wish to send to a server.

type Body

The body represents the main data that you will send in the HTTP request.

withEmptyBody : RequestConfiguration a -> RequestConfiguration a

Removes the body from the RequestConfiguration. You normally don't have to use this function, as an empty body is the default.

If the "Content-Type" header is set, this function will remove it.

withStringBody :
String
-> String
-> RequestConfiguration a
-> RequestConfiguration a

Sets the given string as the request body. You need to provide a mime type to describe what the string contains. This mime type will be set as the "Content-Type" header, potentially overwriting the header if it has already been set.

withJsonBody : Value -> RequestConfiguration a -> RequestConfiguration a

Sets the provided Json value the request body. A "Content-Type" header will be attached to the request with a value of "application/json", potentially overwriting the header if it has already been set.

withBytesBody :
String
-> Bytes
-> RequestConfiguration a
-> RequestConfiguration a

Sets the provided Bytes value as the request body. You need to provide a mime type to desribe what the bytes represent. This mime type will be set as the "Content-Type" header, potentially overwriting the header if it has already been set.

Expected response body

Once a request has been sent, you usually get a response. The Expect type represents what we expect the response body to be.

type Expect a

This describes what you expect the server will respond with when it receives your request.

expectAnything : RequestConfiguration a -> RequestConfiguration {}

Use this when you you don't really care what the server responds with. Anything is fine. Actually, this is the default value so you probably don't need to use this at all.

expectNothing : RequestConfiguration a -> RequestConfiguration {}

Expect exactly nothing. Use this when you want a request to fail if the server responds with anything at all.

expectString : RequestConfiguration a -> RequestConfiguration String

Use this when you expect the server to respond with a string.

expectJson :
Decoder a
-> RequestConfiguration x
-> RequestConfiguration a

Use this when you expect a Json response. The request will fail if the provided decoder fails.

expectBytes : RequestConfiguration a -> RequestConfiguration Bytes

Use this when you want to treat the response as bytes. This will likely never fail, as anything can be interpreted as bytes.

Send

Once your Response is configured, you'll want to actually send the request.

send :
Permission
-> RequestConfiguration expectedBody
-> Task (Error expectedBody)
(Response expectedBody)

Send a request. The task will either complete with a successful Response, or an Error.

type alias Response data =
{ statusCode : Int
, statusText : String
, headers : Dict String (Array String)
, data : data
}

The response from the server.

  • statusCode: A numerical value that gives an indication of how the request went. It might be a good idea to read this list of HTTP status codes.
  • statusText: A human readable interpretation of the status code.
  • headers: The headers returned by the server.
  • data: The data returned by the server. The type depends on the Expect value you set on the request.

Errors

type Error body
= BadUrl String
| BadHeaders
| BadStatus (Response body)
| UnexpectedResponseBody String
| Timeout
| UnknownError String

A HTTP request can fail in a number of ways.

  • BadUrl: Something is wrong with the URL you provided.
  • BadHeaders: The request headers are invalid. Make sure you only use characters in the latin-1 character set.
  • BadStatus: The status code indicates that the response didn't go well. The Response is attached, with a string-encoded data.
  • Timeout: The request timed out. The server didn't respond as quickly as you expected it would.
  • UnknownError: We don't know what went wrong. You should probably report this if you see it in the wild.
errorToString : Error body -> String

Gives a brief description of an error.

Streaming

Streaming is the more advanced way to perform a HTTP request. This requires that you follow the Elm architecture, as you'll receive messages for every chunk of data sent and received. The benefit of this extra complexity, is that you can perform actions while the request is being performed.

type StreamRequest

Identifies a streaming request. Required to perform certain operations while the request is streaming.

type StreamEvent
= SentChunk StreamRequest
| ReceivedChunk ({ request : StreamRequest, response : Response Bytes })
| Error (Error Bytes)
| Aborted
| Done

When a request is streaming, the application is notified of important events and is required to act on those events for the request to be successful.

  • SentChunk: The initial request body, or the last piece of data sent with sendChunk has been sent. Send more data, or call startReceive to begin listening for the response.
  • ReceivedChunk: The server has responded with some data. More data might be coming in, though. The Done event will be triggered when there's no more data coming. You can use the provided Response object to access the response headers, and decide if you'd like to abort the request or not.
  • Error: Something went wrong. More information in the provided Error object.
  • Aborted: You called abort on this request.
  • Done: The server has sent all it's going to send. You're done.
stream :
Permission
-> (StreamEvent -> msg)
-> RequestConfiguration Bytes
-> Cmd msg

Initialize a streaming request. You need to provide a function that generates a message for handling StreamEvents. The headers and data will be sent to the server immedietly, and a SentChunk event will be sent they're done.

To tell different requests apart, you can use a partially applied custom type like this:

type Msg = HttpRequest String StreamEvent

HttpClient.stream httpPermission (HttpRequest "Request 1") requestConfig
sendChunk : StreamRequest -> Bytes -> Cmd msg

Send more data to the server. This allows you to generate more data as you need to, enabling you slice up a potentially costly, memory heavy or long-running operation over time.

You don't have to wait for the matching SentChunk event before sending more data but keep in mind that data will be kept in memory until sent, potentially causing out-of-memory errors in the case of large amounts of data.

If you're already receiving data from the server, calling this function will no effect.

startReceive : StreamRequest -> Cmd msg

Use this when you're done sending data. The server will now begin streaming you the response.

abort : StreamRequest -> Cmd a

Stops the request, for any reason, at any time. Useful if you have an unexpected error with your own source of data, or if the server response is one you know you don't want to handle after having inspected the headers.