Skip to content

HTTP Client

port_http_client

Module port_http_client

This port provides a common interface for using HTTP clients. Many adapters can be developed to use different technologies.

Its main functionalities are:

  • HTTP, HTTPS and HTTP/2 support
  • Mutual TLS
  • Body encoding/decoding
  • Request/response exchange
  • Form submissions
  • Cookie management
  • File uploading/downloading

Install the Dependency

This module is not meant to be used directly. You should include an Adapter implementing this feature (as http_client_ahc) in order to create HTTP clients.

Create an HTTP client

You create an HTTP Client instance with default options as follows:

1
2
3
4
5
6
7
// Adapter injected
Client()                        // No base endpoint, whole URL must be passed each request
Client("http://host:1234/base") // Requests' paths will be appended to supplied base URL

// Adapter provided explicitly
Client(adapter)
Client(adapter, "http://host:1234/base")

Settings

If you want to configure options for the client, you can create it with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// All client settings parameters are optionals and provide default values
Client("http://host:1234/base", ClientSettings(
    contentType = "application/json",
    useCookies = true,
    headers = mapOf("X-Api-Key" to listOf("cafebabe")), // Headers to use in all requests
    user = "user",                                      // HTTP Basic auth user
    password = "password",                              // HTTP Basic auth password
    insecure = false,               // If true, the client doesn't check server certificates
    sslSettings = SslSettings()     // Key stores settings (check TLS section for details)
))

Send generic requests

The most common use case is to send a request and get a response. For details about how to use requests and responses, refer to the Request and the Response API.

Check this code snippet to get a glimpse on how to send the most general requests:

1
2
3
4
5
6
7
8
9
val request = Request(
    method = GET,
    path = "/",
    body = mapOf("body" to "payload"),
    headers = mapOf("X-Header" to listOf("value")),
    contentType = Json.contentType
)

val response = client.send(request)

Simple requests shortcuts

There are utility methods to make the most common request in an easy way.

Without body
1
2
3
4
5
6
7
8
val responseGet = client.get("/")
val responseHead = client.head("/")
val responsePost = client.post("/")
val responsePut = client.put("/")
val responseDelete = client.delete("/")
val responseTrace = client.trace("/")
val responseOptions = client.options("/")
val responsePatch = client.patch("/")
With body
1
2
3
4
5
6
7
8
9
val body = mapOf("key" to "value")

val responseGet = client.get("/", body = body)
val responsePost = client.post("/", body)
val responsePut = client.put("/", body)
val responseDelete = client.delete("/", body)
val responseTrace = client.trace("/", body)
val responseOptions = client.options("/", body)
val responsePatch = client.patch("/", body)
With body and content type
1
2
3
4
5
6
7
8
9
val body = mapOf("key" to "value")

val responseGet = client.get("/", body = body, format = Yaml)
val responsePost = client.post("/", body, Yaml)
val responsePut = client.put("/", body, Yaml)
val responseDelete = client.delete("/", body, Yaml)
val responseTrace = client.trace("/", body, Yaml)
val responseOptions = client.options("/", body, Yaml)
val responsePatch = client.patch("/", body, Yaml)

Cookies

The HTTP client support setting cookies from client side and updates them after any server request. Check the details in the following code fragment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
val cookieName = "sampleCookie"
val cookieValue = "sampleCookieValue"

// Set the cookie in the client
client.cookies["sampleCookie"] = HttpCookie(cookieName, cookieValue)

// Assert that it is received in the server and change its value afterwards
client.post("/assertHasCookie?cookieName=$cookieName")
client.post("/addCookie?cookieName=$cookieName&cookieValue=${cookieValue}_changed")

// Verify that the client cookie is updated
assert(client.cookies[cookieName]?.value == cookieValue + "_changed")

You can also check the full test for more details.

Multipart (forms and files)

Using the HTTP client you can send MIME multipart parts to the server. You can use it to post forms or files.

Forms
1
2
val parts = mapOf("name" to Part("name", "value"))
val response = client.send(Request(POST, "/multipart", parts = parts))
Files
1
2
3
val stream = URL("classpath:assets/index.html").openStream()
val parts = mapOf("file" to Part("file", stream, "index.html"))
val response = client.send(Request(POST, "/file", parts = parts))

TLS

The HTTP client supports server certificates (to use HTTPS and HTTP/2) and also client certificates (to be able to do mutual TLS). Key stores may have the JKS format (deprecated), or the newer PKCS12 format.

To set up client/server certificates, you need to include SslSettings in your ClientSettings. In the sections below you can see how to configure these parameters.

Key Store

This store holds the identity certificate, this certificate is presented to the server by the client in the handshake for the server to authorize or deny the connection. The following code:

1
2
3
4
val keyStoreSettings = SslSettings(
    keyStore = URL("classpath:ssl/$identity"),
    keyStorePassword = identity.reversed()
)
Trust Store

This key store should include all the trusted certificates. Any certificate added as CA (certificate authority) makes the client trust any other certificate signed by them. However, you can also add standalone server certificates.

1
2
3
4
val trustStoreSettings = SslSettings(
    trustStore = URL("classpath:ssl/$trust"),
    trustStorePassword = trust.reversed()
)

Mutual TLS

If you set up the identity (service's own certificate) and the trust store (CAs and servers trusted by the client), you will achieve double ended authentication (server authenticated by the client, and client authenticated by the server). You can see a complete example below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Key store files
val identity = "hexagonkt.p12"
val trust = "trust.p12"

// Default passwords are file name reversed
val keyStorePassword = identity.reversed()
val trustStorePassword = trust.reversed()

// Key stores can be set as URIs to classpath resources (the triple slash is needed)
val keyStore = URL("classpath:ssl/$identity")
val trustStore = URL("classpath:ssl/$trust")

val sslSettings = SslSettings(
    keyStore = keyStore,
    keyStorePassword = keyStorePassword,
    trustStore = trustStore,
    trustStorePassword = trustStorePassword,
    clientAuth = true // Requires a valid certificate from the client (mutual TLS)
)

val serverSettings = ServerSettings(
    bindPort = 0,
    protocol = HTTPS, // You can also use HTTP2
    sslSettings = sslSettings
)

val server = serve(serverSettings, serverAdapter) {
    get("/hello") {
        // We can access the certificate used by the client from the request
        val subjectDn = request.certificate?.subjectDN?.name
        response.headers["cert"] = subjectDn
        ok("Hello World!")
    }
}

// We'll use the same certificate for the client (in a real scenario it would be different)
val clientSettings = ClientSettings(sslSettings = sslSettings)

// Create a HTTP client and make a HTTPS request
val client = Client(AhcAdapter(), "https://localhost:${server.runtimePort}", clientSettings)
client.get("/hello").apply {
    logger.debug { body }
    // Assure the certificate received (and returned) by the server is correct
    assert(headers.require("cert").first().startsWith("CN=hexagonkt.com"))
    assert(body == "Hello World!")
}

Package com.hexagonkt.http.client

This package holds the classes that define the HTTP client and its configuration settings.

Packages

Name Summary
com.hexagonkt.http.client

Index

All Types

Comments