HttpClient.kt

  1. package com.hexagonkt.http.client

  2. import com.hexagonkt.http.handlers.HttpContext
  3. import com.hexagonkt.http.handlers.HttpHandler
  4. import com.hexagonkt.http.handlers.OnHandler
  5. import com.hexagonkt.http.handlers.path
  6. import com.hexagonkt.http.model.HttpRequest
  7. import com.hexagonkt.http.model.HttpResponsePort
  8. import com.hexagonkt.http.model.*
  9. import com.hexagonkt.http.model.HttpMethod.*
  10. import com.hexagonkt.http.model.ws.WsSession
  11. import java.io.Closeable
  12. import java.util.concurrent.Flow.Publisher

  13. /**
  14.  * Client to use other REST services.
  15.  */
  16. class HttpClient(
  17.     private val adapter: HttpClientPort,
  18.     val settings: HttpClientSettings = HttpClientSettings(),
  19.     val handler: HttpHandler? = null,
  20. ) : Closeable {

  21.     private val rootHandler: HttpHandler? =
  22.         handler?.let {
  23.             val sendHandler = OnHandler("*") { send(adapter.send(request)) }
  24.             path("*", listOf(it, sendHandler))
  25.         }

  26.     private val noRequestSettings =
  27.         settings.contentType == null
  28.             && settings.authorization == null
  29.             && settings.accept.isEmpty()
  30.             && settings.headers.isEmpty()

  31.     var cookies: List<Cookie> = emptyList()

  32.     override fun close() {
  33.         stop()
  34.     }

  35.     fun cookiesMap(): Map<String, Cookie> =
  36.         cookies.associateBy { it.name }

  37.     fun start() {
  38.         check(!started()) { "HTTP client is already started" }
  39.         adapter.startUp(this)
  40.     }

  41.     fun stop() {
  42.         check(started()) { "HTTP client *MUST BE STARTED* before shut-down" }
  43.         adapter.shutDown()
  44.     }

  45.     fun started(): Boolean =
  46.         adapter.started()

  47.     /**
  48.      * Synchronous execution.
  49.      */
  50.     fun send(request: HttpRequest, attributes: Map<String, Any> = emptyMap()): HttpResponsePort =
  51.         if (!started())
  52.             error("HTTP client *MUST BE STARTED* before sending requests")
  53.         else
  54.             rootHandler
  55.                 ?.process(request.setUp(), attributes)
  56.                 ?.let {
  57.                     if (it.exception != null) throw it.exception as Exception
  58.                     else it.response
  59.                 }
  60.                 ?: adapter.send(request.setUp())

  61.     private fun HttpRequest.setUp(): HttpRequest {
  62.         return if (noRequestSettings)
  63.             this
  64.         else
  65.             copy(
  66.                 contentType = contentType ?: settings.contentType,
  67.                 accept = accept.ifEmpty(settings::accept),
  68.                 headers = settings.headers + headers,
  69.                 authorization = authorization ?: settings.authorization,
  70.             )
  71.     }

  72.     fun sse(request: HttpRequest): Publisher<ServerEvent> =
  73.         if (!started()) error("HTTP client *MUST BE STARTED* before sending requests")
  74.         else adapter.sse(request)

  75.     fun sse(path: String): Publisher<ServerEvent> =
  76.         sse(HttpRequest(path = path))

  77.     fun ws(
  78.         path: String,
  79.         onConnect: WsSession.() -> Unit = {},
  80.         onBinary: WsSession.(data: ByteArray) -> Unit = {},
  81.         onText: WsSession.(text: String) -> Unit = {},
  82.         onPing: WsSession.(data: ByteArray) -> Unit = {},
  83.         onPong: WsSession.(data: ByteArray) -> Unit = {},
  84.         onClose: WsSession.(status: Int, reason: String) -> Unit = { _, _ -> },
  85.     ): WsSession =
  86.         if (!started()) error("HTTP client *MUST BE STARTED* before connecting to WS")
  87.         else adapter.ws(path, onConnect, onBinary, onText, onPing, onPong, onClose)

  88.     fun request (block: HttpClient.() -> Unit) {
  89.         if (!started())
  90.             start()

  91.         use(block)
  92.     }

  93.     fun get(
  94.         path: String = "",
  95.         headers: Headers = Headers(),
  96.         body: Any? = null,
  97.         contentType: ContentType? = settings.contentType,
  98.         accept: List<ContentType> = settings.accept,
  99.     ): HttpResponsePort =
  100.             send(
  101.                 HttpRequest(
  102.                     method = GET,
  103.                     path = path,
  104.                     body = body ?: "",
  105.                     headers = headers,
  106.                     contentType = contentType,
  107.                     accept = accept,
  108.                 )
  109.             )

  110.     fun head(path: String = "", headers: Headers = Headers()): HttpResponsePort =
  111.         send(HttpRequest(HEAD, path = path, body = ByteArray(0), headers = headers))

  112.     fun post(
  113.         path: String = "",
  114.         body: Any? = null,
  115.         contentType: ContentType? = settings.contentType,
  116.         accept: List<ContentType> = settings.accept,
  117.     ): HttpResponsePort =
  118.         send(
  119.             HttpRequest(
  120.                 method = POST,
  121.                 path = path,
  122.                 body = body ?: "",
  123.                 contentType = contentType,
  124.                 accept = accept,
  125.             )
  126.         )

  127.     fun put(
  128.         path: String = "",
  129.         body: Any? = null,
  130.         contentType: ContentType? = settings.contentType,
  131.         accept: List<ContentType> = settings.accept,
  132.     ): HttpResponsePort =
  133.         send(
  134.             HttpRequest(
  135.                 method = PUT,
  136.                 path = path,
  137.                 body = body ?: "",
  138.                 contentType = contentType,
  139.                 accept = accept,
  140.             )
  141.         )

  142.     fun delete(
  143.         path: String = "",
  144.         body: Any? = null,
  145.         contentType: ContentType? = settings.contentType,
  146.         accept: List<ContentType> = settings.accept,
  147.     ): HttpResponsePort =
  148.         send(
  149.             HttpRequest(
  150.                 method = DELETE,
  151.                 path = path,
  152.                 body = body ?: "",
  153.                 contentType = contentType,
  154.                 accept = accept,
  155.             )
  156.         )

  157.     fun trace(
  158.         path: String = "",
  159.         body: Any? = null,
  160.         contentType: ContentType? = settings.contentType,
  161.         accept: List<ContentType> = settings.accept,
  162.     ): HttpResponsePort =
  163.         send(
  164.             HttpRequest(
  165.                 method = TRACE,
  166.                 path = path,
  167.                 body = body ?: "",
  168.                 contentType = contentType,
  169.                 accept = accept,
  170.             )
  171.         )

  172.     fun options(
  173.         path: String = "",
  174.         body: Any? = null,
  175.         headers: Headers = Headers(),
  176.         contentType: ContentType? = settings.contentType,
  177.         accept: List<ContentType> = settings.accept,
  178.     ): HttpResponsePort =
  179.         send(
  180.             HttpRequest(
  181.                 method = OPTIONS,
  182.                 path = path,
  183.                 body = body ?: "",
  184.                 headers = headers,
  185.                 contentType = contentType,
  186.                 accept = accept,
  187.             )
  188.         )

  189.     fun patch(
  190.         path: String = "",
  191.         body: Any? = null,
  192.         contentType: ContentType? = settings.contentType,
  193.         accept: List<ContentType> = settings.accept,
  194.     ): HttpResponsePort =
  195.         send(
  196.             HttpRequest(
  197.                 method = PATCH,
  198.                 path = path,
  199.                 body = body ?: "",
  200.                 contentType = contentType,
  201.                 accept = accept,
  202.             )
  203.         )

  204.     private fun HttpHandler.process(
  205.         request: HttpRequestPort, attributes: Map<String, Any>
  206.     ): HttpContext =
  207.         processHttp(
  208.             HttpContext(HttpCall(request = request), handlerPredicate, attributes = attributes)
  209.         )
  210. }