HttpClient.kt

  1. package com.hexagontk.http.client

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

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

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

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

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

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

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

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

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

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

  46.     fun request (block: HttpClient.() -> Unit) {
  47.         if (!started())
  48.             start()

  49.         use(block)
  50.     }

  51.     /**
  52.      * Synchronous execution.
  53.      */
  54.     fun send(request: HttpRequest): HttpResponsePort =
  55.         if (!started())
  56.             error("HTTP client *MUST BE STARTED* before sending requests")
  57.         else
  58.             rootHandler
  59.                 ?.process(request.setUp())
  60.                 ?.let {
  61.                     if (it.exception != null) throw it.exception as Exception
  62.                     else it.response
  63.                 }
  64.                 ?: adapter.send(request.setUp())

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

  68.     fun sse(path: String): Publisher<ServerEvent> =
  69.         sse(HttpRequest(path = path))

  70.     fun ws(
  71.         path: String,
  72.         onConnect: WsSession.() -> Unit = {},
  73.         onBinary: WsSession.(data: ByteArray) -> Unit = {},
  74.         onText: WsSession.(text: String) -> Unit = {},
  75.         onPing: WsSession.(data: ByteArray) -> Unit = {},
  76.         onPong: WsSession.(data: ByteArray) -> Unit = {},
  77.         onClose: WsSession.(status: Int, reason: String) -> Unit = { _, _ -> },
  78.     ): WsSession =
  79.         if (!started()) error("HTTP client *MUST BE STARTED* before connecting to WS")
  80.         else adapter.ws(path, onConnect, onBinary, onText, onPing, onPong, onClose)

  81.     fun get(
  82.         path: String = "",
  83.         headers: Headers = Headers(),
  84.         body: Any? = null,
  85.         contentType: ContentType? = settings.contentType,
  86.         accept: List<ContentType> = settings.accept,
  87.     ): HttpResponsePort =
  88.         send(
  89.             HttpRequest(
  90.                 method = GET,
  91.                 path = path,
  92.                 body = body ?: "",
  93.                 headers = headers,
  94.                 contentType = contentType,
  95.                 accept = accept,
  96.             )
  97.         )

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

  100.     fun post(
  101.         path: String = "",
  102.         body: Any? = null,
  103.         contentType: ContentType? = settings.contentType,
  104.         accept: List<ContentType> = settings.accept,
  105.     ): HttpResponsePort =
  106.         send(
  107.             HttpRequest(
  108.                 method = POST,
  109.                 path = path,
  110.                 body = body ?: "",
  111.                 contentType = contentType,
  112.                 accept = accept,
  113.             )
  114.         )

  115.     fun put(
  116.         path: String = "",
  117.         body: Any? = null,
  118.         contentType: ContentType? = settings.contentType,
  119.         accept: List<ContentType> = settings.accept,
  120.     ): HttpResponsePort =
  121.         send(
  122.             HttpRequest(
  123.                 method = PUT,
  124.                 path = path,
  125.                 body = body ?: "",
  126.                 contentType = contentType,
  127.                 accept = accept,
  128.             )
  129.         )

  130.     fun delete(
  131.         path: String = "",
  132.         body: Any? = null,
  133.         contentType: ContentType? = settings.contentType,
  134.         accept: List<ContentType> = settings.accept,
  135.     ): HttpResponsePort =
  136.         send(
  137.             HttpRequest(
  138.                 method = DELETE,
  139.                 path = path,
  140.                 body = body ?: "",
  141.                 contentType = contentType,
  142.                 accept = accept,
  143.             )
  144.         )

  145.     fun trace(
  146.         path: String = "",
  147.         body: Any? = null,
  148.         contentType: ContentType? = settings.contentType,
  149.         accept: List<ContentType> = settings.accept,
  150.     ): HttpResponsePort =
  151.         send(
  152.             HttpRequest(
  153.                 method = TRACE,
  154.                 path = path,
  155.                 body = body ?: "",
  156.                 contentType = contentType,
  157.                 accept = accept,
  158.             )
  159.         )

  160.     fun options(
  161.         path: String = "",
  162.         body: Any? = null,
  163.         headers: Headers = Headers(),
  164.         contentType: ContentType? = settings.contentType,
  165.         accept: List<ContentType> = settings.accept,
  166.     ): HttpResponsePort =
  167.         send(
  168.             HttpRequest(
  169.                 method = OPTIONS,
  170.                 path = path,
  171.                 body = body ?: "",
  172.                 headers = headers,
  173.                 contentType = contentType,
  174.                 accept = accept,
  175.             )
  176.         )

  177.     fun patch(
  178.         path: String = "",
  179.         body: Any? = null,
  180.         contentType: ContentType? = settings.contentType,
  181.         accept: List<ContentType> = settings.accept,
  182.     ): HttpResponsePort =
  183.         send(
  184.             HttpRequest(
  185.                 method = PATCH,
  186.                 path = path,
  187.                 body = body ?: "",
  188.                 contentType = contentType,
  189.                 accept = accept,
  190.             )
  191.         )

  192.     private fun HttpRequestPort.setUp(): HttpRequestPort {
  193.         return if (noRequestSettings)
  194.             this
  195.         else
  196.             with(
  197.                 contentType = contentType ?: settings.contentType,
  198.                 accept = accept.ifEmpty(settings::accept),
  199.                 headers = settings.headers + headers,
  200.                 authorization = authorization ?: settings.authorization,
  201.             )
  202.     }
  203. }