HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Modulehttp-streams-0.8.9.9Haskell2010

Network.Http.Connection

  • 2 types
  • 20 values
valuemakeConnection
  1. :: ByteString

    will be used as the Host: header in the HTTP request.

  2. -> IO ()

    an action to be called when the connection is terminated.

  3. -> OutputStream ByteString

    write end of the HTTP client-server connection.

  4. -> InputStream ByteString

    read end of the HTTP client-server connection.

  5. -> IO Connection
#

Create a raw Connection object from the given parts. This is primarily of use when teseting, for example:

fakeConnection :: IO Connection
fakeConnection = do
    o  <- Streams.nullOutput
    i  <- Streams.nullInput
    c  <- makeConnection "www.example.com" (return()) o i
    return c

is an idiom we use frequently in testing and benchmarking, usually replacing the InputStream with something like:

    x' <- S.readFile "properly-formatted-response.txt"
    i  <- Streams.fromByteString x'

If you're going to do that, keep in mind that you must have CR-LF pairs after each header line and between the header and body to be compliant with the HTTP protocol; otherwise, parsers will reject your message.

valuewithConnection :: IO Connection -> (Connection -> IO γ) -> IO γ
#

Given an IO action producing a Connection, and a computation that needs one, runs the computation, cleaning up the Connection afterwards.

    x <- withConnection (openConnection "s3.example.com" 80) $ (\c -> do
        let q = buildRequest1 $ do
            http GET "/bucket42/object/149"
        sendRequest c q emptyBody
        ...
        return "blah")

which can make the code making an HTTP request a lot more straight-forward.

Wraps Control.Exception's bracket.

In order to make a request you first establish the TCP connection to the server over which to send it.

Ordinarily you would supply the host part of the URL here and it will be used as the value of the HTTP 1.1 Host: field. However, you can specify any server name or IP addresss and set the Host: value later with setHostname when building the request.

Usage is as follows:

    c <- openConnection "localhost" 80
    ...
    closeConnection c

More likely, you'll use withConnection to wrap the call in order to ensure finalization.

HTTP pipelining is supported; you can reuse the connection to a web server, but it's up to you to ensure you match the number of requests sent to the number of responses read, and to process those responses in order. This is all assuming that the server supports pipelining; be warned that not all do. Web browsers go to extraordinary lengths to probe this; you probably only want to do pipelining under controlled conditions. Otherwise just open a new connection for subsequent requests.

Open a secure connection to a web server.

import OpenSSL (withOpenSSL)

main :: IO ()
main = do
    ctx <- baselineContextSSL
    c <- openConnectionSSL ctx "api.github.com" 443
    ...
    closeConnection c

If you want to tune the parameters used in making SSL connections, manually specify certificates, etc, then setup your own context:

import OpenSSL.Session (SSLContext)
import qualified OpenSSL.Session as SSL

    ...
    ctx <- SSL.context
    ...

See OpenSSL.Session.

Crypto is as provided by the system openssl library, as wrapped by the HsOpenSSL package and openssl-streams.

/There is no longer a need to call withOpenSSL explicitly; the initialization is invoked once per process for you/

valuecloseConnection :: Connection -> IO ()
#

Shutdown the connection. You need to call this release the underlying socket file descriptor and related network resources. To do so reliably, use this in conjunction with openConnection in a call to bracket:

--
-- Make connection, cleaning up afterward
--

foo :: IO ByteString
foo = bracket
   (openConnection "localhost" 80)
   (closeConnection)
   (doStuff)

--
-- Actually use Connection to send Request and receive Response
--

doStuff :: Connection -> IO ByteString

or, just use withConnection.

While returning a ByteString is probably the most common use case, you could conceivably do more processing of the response in doStuff and have it and foo return a different type.

Get the virtual hostname that will be used as the Host: header in the HTTP 1.1 request. Per RFC 2616 § 14.23, this will be of the form hostname:port if the port number is other than the default, ie 80 for HTTP.

Get the headers that will be sent with this request. You likely won't need this but there are some corner cases where people need to make calculations based on all the headers before they go out over the wire.

If you'd like the request headers as an association list, import the header functions:

import Network.Http.Types

then use Network.Http.Types.retreiveHeaders as follows:

Example2 expressions
let kvs = retreiveHeaders $ getHeadersFull c q:t kvs:: [(ByteString, ByteString)]

Having composed a Request object with the headers and metadata for this connection, you can now send the request to the server, along with the entity body, if there is one. For the rather common case of HTTP requests like GET that don't send data, use emptyBody as the output stream:

    sendRequest c q emptyBody

For PUT and POST requests, you can use fileBody or inputStreamBody to send content to the server, or you can work with the io-streams API directly:

    sendRequest c q (\o ->
        Streams.write (Just (Builder.fromString "Hello World\n")) o)

Handle the response coming back from the server. This function hands control to a handler function you supply, passing you the Response object with the response headers and an InputStream containing the entity body.

For example, if you just wanted to print the first chunk of the content from the server:

    receiveResponse c (\p i -> do
        m <- Streams.read i
        case m of
            Just bytes -> putStr bytes
            Nothing    -> return ())

Obviously, you can do more sophisticated things with the InputStream, which is the whole point of having an io-streams based HTTP client library.

The final value from the handler function is the return value of receiveResponse, if you need it.

Throws UnexpectedCompression if it doesn't know how to handle the compression format used in the response.

Handle the response coming back from the server. This function is the same as receiveResponse, but it does not consume the body for you after the handler is done. This means that it can only be safely used if the handler will fully consume the body, there is no body, or when the connection is not being reused (no pipelining).

valueemptyBody :: OutputStream Builder -> IO ()
#

Use this for the common case of the HTTP methods that only send headers and which have no entity body, i.e. GET requests.

Sometimes you just want to send some bytes to the server as a the body of your request. This is easy to use, but if you're doing anything massive use inputStreamBody; if you're sending a file use fileBody; if you have an object that needs to be sent as JSON use jsonBody

valuefileBody :: FilePath -> OutputStream Builder -> IO ()
#

Specify a local file to be sent to the server as the body of the request.

You use this partially applied:

    sendRequest c q (fileBody "/etc/passwd")

Note that the type of (fileBody "/path/to/file") is just what you need for the third argument to sendRequest, namely

Example1 expression
:t filePath "hello.txt":: OutputStream Builder -> IO ()

Read from a pre-existing InputStream and pipe that through to the connection to the server. This is useful in the general case where something else has handed you stream to read from and you want to use it as the entity body for the request.

You use this partially applied:

    i <- getStreamFromVault                    -- magic, clearly
    sendRequest c q (inputStreamBody i)

This function maps "Builder.fromByteString" over the input, which will be efficient if the ByteString chunks are large.

Print the response headers and response body to stdout. You can use this with receiveResponse or one of the convenience functions when testing. For example, doing:

    c <- openConnection "kernel.operationaldynamics.com" 58080

    let q = buildRequest1 $ do
                http GET "/time"

    sendRequest c q emptyBody

    receiveResponse c debugHandler

would print out:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain
Vary: Accept-Encoding
Server: Snap/0.9.2.4
Content-Encoding: gzip
Date: Mon, 21 Jan 2013 06:13:37 GMT

Mon 21 Jan 13, 06:13:37.303Z

or thereabouts.

Sometimes you just want the entire response body as a single blob. This function concatonates all the bytes from the response into a ByteString. If using the main http-streams API, you would use it as follows:

   ...
   x' <- receiveResponse c simpleHandler
   ...

The methods in the convenience API all take a function to handle the response; this function is passed directly to the receiveResponse call underlying the request. Thus this utility function can be used for get as well:

   x' <- get "http://www.example.com/document.txt" simpleHandler

Either way, the usual caveats about allocating a single object from streaming I/O apply: do not use this if you are not absolutely certain that the response body will fit in a reasonable amount of memory.

Note that this function makes no discrimination based on the response's HTTP status code. You're almost certainly better off writing your own handler function.