All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hexagonkt.http.test.examples.SamplesTest.kt Maven / Gradle / Ivy

package com.hexagonkt.http.test.examples

import com.hexagonkt.core.media.APPLICATION_JSON
import com.hexagonkt.core.media.APPLICATION_XML
import com.hexagonkt.core.media.TEXT_CSS
import com.hexagonkt.core.media.TEXT_HTML
import com.hexagonkt.core.urlOf
import com.hexagonkt.http.client.HttpClient
import com.hexagonkt.http.client.HttpClientPort
import com.hexagonkt.http.client.HttpClientSettings
import com.hexagonkt.http.model.HttpRequest
import com.hexagonkt.http.model.HttpResponsePort
import com.hexagonkt.http.model.*
import com.hexagonkt.http.model.HttpMethod.*
import com.hexagonkt.http.model.HttpMethod.Companion.ALL
import com.hexagonkt.http.model.FOUND_302
import com.hexagonkt.http.model.HTTP_VERSION_NOT_SUPPORTED_505
import com.hexagonkt.http.model.INTERNAL_SERVER_ERROR_500
import com.hexagonkt.http.model.OK_200
import com.hexagonkt.http.server.HttpServer
import com.hexagonkt.http.server.HttpServerPort
import com.hexagonkt.http.server.HttpServerSettings
import com.hexagonkt.http.server.callbacks.UrlCallback
import com.hexagonkt.http.handlers.*
import com.hexagonkt.http.server.serve
import org.junit.jupiter.api.Test
import java.net.InetAddress
import kotlin.test.assertEquals

abstract class SamplesTest(
    val clientAdapter: () -> HttpClientPort,
    val serverAdapter: () -> HttpServerPort,
    val serverSettings: HttpServerSettings = HttpServerSettings(),
) {

    @Test fun serverCreation() {
        // serverCreation
        /*
         * All settings are optional, you can supply any combination
         * Parameters not set will fall back to the defaults
         */
        val settings = HttpServerSettings(
            bindAddress = InetAddress.getByName("0.0.0"),
            bindPort = 2020,
            contextPath = "/context",
            banner = "name"
        )

        val path = path {
            get("/hello") { ok("Hello World!") }
        }

        val runningServer = serve(serverAdapter(), path, settings)

        // Servers implement closeable, you can use them inside a block assuring they will be closed
        runningServer.use { s ->
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()
                assert(s.started())
                assertEquals("Hello World!", it.get("/context/hello").body)
            }
        }

        /*
         * You may skip the settings and the defaults will be used
         */
        val defaultSettingsServer = serve(serverAdapter(), path)
        // serverCreation

        defaultSettingsServer.use { s ->
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()
                assert(s.started())
                assertEquals("Hello World!", it.get("/hello").body)
            }
        }
    }

    @Test fun routesCreation() {
        val server = serve(serverAdapter()) {
            // routesCreation
            get("/hello") { ok("Get greeting") }
            put("/hello") { ok("Put greeting") }
            post("/hello") { ok("Post greeting") }

            on(ALL - GET - PUT - POST, "/hello") { ok("Fallback if HTTP verb was not used before") }

            on(status = NOT_FOUND_404) { ok("Get at '/' if no route matched before") }
            // routesCreation
        }

        server.use { s ->
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()
                assertEquals("Get greeting", it.get("/hello").body)
                assertEquals("Put greeting", it.put("/hello").body)
                assertEquals("Post greeting", it.post("/hello").body)
                assertEquals("Fallback if HTTP verb was not used before", it.options("/hello").body)
                assertEquals("Get at '/' if no route matched before", it.get("/").body)
            }
        }
    }

    @Test fun routeGroups() {
        val server = serve(serverAdapter()) {
            // routeGroups
            path("/nested") {
                get("/hello") { ok("Greeting") }

                path("/secondLevel") {
                    get("/hello") { ok("Second level greeting") }
                }

                get { ok("Get at '/nested'") }
            }
            // routeGroups
        }

        server.use { s ->
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()
                assertEquals("Greeting", it.get("/nested/hello").body)
                assertEquals("Second level greeting", it.get("/nested/secondLevel/hello").body)
                assertEquals("Get at '/nested'", it.get("/nested").body)
            }
        }
    }

    @Test fun routers() {
        // routers
        fun personRouter(kind: String) = path {
            get { ok("Get $kind") }
            put { ok("Put $kind") }
            post { ok("Post $kind") }
        }

        val server = HttpServer(serverAdapter()) {
            path("/clients", personRouter("client"))
            path("/customers", personRouter("customer"))
        }
        // routers

        server.use { s ->
            s.start()
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()

                assertEquals("Get client", it.get("/clients").body)
                assertEquals("Put client", it.put("/clients").body)
                assertEquals("Post client", it.post("/clients").body)

                assertEquals("Get customer", it.get("/customers").body)
                assertEquals("Put customer", it.put("/customers").body)
                assertEquals("Post customer", it.post("/customers").body)
            }
        }
    }

    @Test fun callbacks() {
        val server = HttpServer(serverAdapter()) {
            // callbackCall
            get("/call") {
                attributes          // The attributes list
                attributes["foo"]   // Value of foo attribute

                ok("Response body") // Returns a 200 status
                // return any status (previous return value is ignored here)
                send(
                    BAD_REQUEST_400,
                    "Invalid request",
                    attributes = attributes + ("A" to "V") // Sets value of attribute A to V
                )
            }
            // callbackCall

            // callbackRequest
            get("/request") {
                // URL Information
                request.method                   // The HTTP method (GET, etc.)
                request.protocol                 // HTTP or HTTPS
                request.host                     // The host, e.g. "example.com"
                request.port                     // The server port
                request.path                     // The request path, e.g. /result.jsp
                request.body                     // Request body sent by the client

                method                           // Shortcut of `request.method`
                protocol                         // Shortcut of `request.protocol`
                host                             // Shortcut of `request.host`
                port                             // Shortcut of `request.port`
                path                             // Shortcut of `request.path`

                // Headers
                request.headers                  // The HTTP headers map
                request.headers["BAR"]?.value    // First value of BAR header
                request.headers["BAR"]?.values   // List of values of BAR header

                // Common headers shortcuts
                request.contentType              // Content type of request.body
                request.accept                   // Client accepted content types
                request.authorization            // Client authorization
                request.userAgent()              // User agent (browser requests)
                request.origin()                 // Origin (browser requests)
                request.referer()                // Referer header (page that makes the request)

                accept                           // Shortcut of `request.accept`
                authorization                    // Shortcut of `request.authorization`

                // Parameters
                pathParameters                    // Map with all path parameters
                request.formParameters            // Map with all form fields
                request.queryParameters           // Map with all query parameters

                queryParameters                   // Shortcut of `request.queryParameters`
                formParameters                    // Shortcut of `request.formParameters`

                // Body processing
                request.contentLength             // Length of request body

                ok()
            }
            // callbackRequest

            // callbackResponse
            get("/response") {
                response.body                     // Get response content
                response.status                   // Get the response status
                response.contentType              // Get the content type

                status                            // Shortcut of `response.status`

                send(
                    status = UNAUTHORIZED_401,                  // Set status code to 401
                    body = "Hello",                             // Sets content to Hello
                    contentType = ContentType(APPLICATION_XML), // Set application/xml content type
                    headers = response.headers
                        + Header("foo", "bar")    // Sets header FOO with single value bar
                        + Header("baz", "1", "2") // Sets header FOO values with [ bar ]
                )

                // Utility methods for generating common responses
                unauthorized("401: authorization missing")
                forbidden("403: access not granted")
                internalServerError("500: server error")
                serverError(NOT_IMPLEMENTED_501, RuntimeException("Error"))
                ok("Correct")
                badRequest("400: incorrect request")
                notFound("404: Missing resource")
                created("201: Created")
                redirect(FOUND_302, "/location")
                found("/location")

                // The response can be modified chaining send calls (or its utility methods)
                ok("Replacing headers").send(headers = Headers())

                // If calls are not chained, only the last one will be applied
                ok("Intending to replace headers")
                send(headers = Headers()) // This will be passed, but previous ok will be ignored
            }
            // callbackResponse

            // callbackPathParam
            get("/pathParam/{foo}") {
                pathParameters["foo"] // Value of foo path parameter
                pathParameters        // Map with all parameters
                ok()
            }
            // callbackPathParam

            // callbackQueryParam
            get("/queryParam") {
                request.queryParameters                      // The query params map
                request.queryParameters["FOO"]?.value        // Value of FOO query param
                request.queryParameters["FOO"]?.values       // All values of FOO query param
                request.queryParameters.values               // The query params list

                ok()
            }
            // callbackQueryParam

            // callbackFormParam
            get("/formParam") {
                request.formParameters                       // The form params map
                request.formParameters["FOO"]?.value         // Value of FOO form param
                request.formParameters["FOO"]?.values        // All values of FOO form param
                request.formParameters.values                // The form params list
                ok()
            }
            // callbackFormParam

            // callbackFile
            post("/file") {
                val filePart = request.partsMap()["file"] ?: error("File not available")
                ok(filePart.body)
            }
            // callbackFile

            // callbackRedirect
            get("/redirect") {
                send(FOUND_302, "/call") // browser redirect to /call
            }
            // callbackRedirect

            // callbackCookie
            get("/cookie") {
                request.cookies                     // Get map of all request cookies
                request.cookiesMap()["foo"]         // Access request cookie by name

                val cookie = Cookie("new_foo", "bar")
                ok(
                    cookies = listOf(
                        cookie,                     // Set cookie with a value
                        cookie.copy(maxAge = 3600), // Set cookie with a max-age
                        cookie.copy(secure = true), // Secure cookie
                        cookie.delete(),            // Remove cookie
                    )
                )
            }
            // callbackCookie

            // callbackHalt
            get("/halt") {
                send(UNAUTHORIZED_401)              // Halt with status
                send(UNAUTHORIZED_401, "Go away!")  // Halt with status and message
                internalServerError("Body Message") // Halt with message (status 500)
                internalServerError()               // Halt with status 500
            }
            // callbackHalt
        }

        server.use { s ->
            s.start()
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.cookies += Cookie("foo", "bar")
                it.start()

                val json = ContentType(APPLICATION_JSON)
                val callResponse = it.get("/call")
                assertEquals(BAD_REQUEST_400, callResponse.status)
                assertEquals("Invalid request", callResponse.body)
                assertEquals(OK_200, it.get("/request", body = "body", contentType = json).status)

                assertEquals(NOT_FOUND_404, it.get("/response").status)
                assertEquals(OK_200, it.get("/pathParam/param").status)
                assertEquals(OK_200, it.get("/queryParam").status)
                assertEquals(OK_200, it.get("/formParam").status)
                assertEquals(FOUND_302, it.get("/redirect").status)
                assertEquals(OK_200, it.get("/cookie").status)
                assertEquals(INTERNAL_SERVER_ERROR_500, it.get("/halt").status)

                val stream = urlOf("classpath:assets/index.html").readBytes()
                val parts = listOf(HttpPart("file", stream, "index.html"))
                val response = it.send(HttpRequest(POST, path = "/file", parts = parts))
                assert(response.bodyString().contains("Hexagon"))
            }
        }
    }

    @Test fun filters() {
        fun assertResponse(response: HttpResponsePort, body: String, vararg headers: String) {
            assertEquals(OK_200, response.status)
            assertEquals(body, response.body)
            (headers.toList() + "b-all" + "a-all").forEach {
                assert(response.headers.httpFields.containsKey(it))
            }
        }

        fun assertFail(
            code: HttpStatus,
            response: HttpResponsePort,
            body: String,
            vararg headers: String
        ) {
            assertEquals(code, response.status)
            (headers.toList() + "b-all" + "a-all")
                .forEach { assert(response.headers.httpFields.contains(it)) }
            assertEquals(body, response.body)
        }

        val server = HttpServer(serverAdapter()) {
            // filters
            before("/*") { send(response + Header("b-all", "true")) }

            before("/filters/*") { send(response + Header("b-filters", "true")) }
            get("/filters/route") { ok("filters route") }
            after("/filters/*") { send(response + Header("a-filters", "true")) }

            get("/filters") { ok("filters") }

            path("/nested") {
                before("*") { send(response + Header("b-nested", "true")) }
                before { send(response + Header("b-nested-2", "true")) }
                get("/filters") { ok("nested filters") }
                get("/halted") { send(HttpStatus(499), "halted") }
                get { ok("nested also") }
                after("*") { send(response + Header("a-nested", "true")) }
            }

            after("/*") { send(response + Header("a-all", "true")) }
            // filters
        }

        server.use { s ->
            s.start()

            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()
                assertResponse(it.get("/filters/route"), "filters route", "b-filters", "a-filters")
                assertResponse(it.get("/filters"), "filters")
                assertResponse(it.get("/nested/filters"), "nested filters", "b-nested", "a-nested")
                val responseNested = it.get("/nested")
                assertResponse(responseNested, "nested also", "b-nested", "b-nested-2", "a-nested")
                val responseHalted = it.get("/nested/halted")
                assertFail(HttpStatus(499), responseHalted, "halted", "b-nested", "a-nested")
                assert(!it.get("/filters/route").headers.httpFields.contains("b-nested"))
                assert(!it.get("/filters/route").headers.httpFields.contains("a-nested"))
            }
        }
    }

    @Test fun errors() {
        class NumberException (val number: Int) : RuntimeException()

        val server = serve(serverAdapter()) {
            // errors
            exception {
                internalServerError("Root handler")
            }

            // Register handler for routes halted with 512 code
            get("/errors") { send(HttpStatus(512)) }

            before(pattern = "*", status = HttpStatus(512)) { send(INTERNAL_SERVER_ERROR_500, "Ouch") }
            // errors

            exception { e ->
                internalServerError(e.number.toString())
            }

            get("/codeException") {
                throw NumberException(9)
            }

            // exceptions
            // Register handler for routes which callbacks throw exceptions
            get("/exceptions") { error("Message") }
            get("/codedExceptions") { send(HttpStatus(509), "code") }

            before(pattern = "*", status = HttpStatus(509)) {
                send(HttpStatus(599))
            }
            exception {
                send(HTTP_VERSION_NOT_SUPPORTED_505, exception?.message ?: "empty")
            }
            // exceptions
        }

        server.use { s ->
            val settings = HttpClientSettings(s.binding)
            HttpClient(clientAdapter(), settings).use {
                it.start()

                val errors = it.get("/errors")
                assertEquals(INTERNAL_SERVER_ERROR_500, errors.status)
                assertEquals("Ouch", errors.body)

                val exceptions = it.get("/exceptions")
                assertEquals(HTTP_VERSION_NOT_SUPPORTED_505, exceptions.status)
                assertEquals("Message", exceptions.body)

                val codedExceptions = it.get("/codedExceptions")
                assertEquals(HttpStatus(599), codedExceptions.status)
                assertEquals("code", codedExceptions.body)

                it.get("/codeException").apply {
                    assertEquals(INTERNAL_SERVER_ERROR_500, status)
                    assertEquals("9", bodyString())
                }
            }
        }
    }

    @Test fun files() {
        val server = serve(serverAdapter()) {
            // files
            get("/web/file.txt") { ok("It matches this route and won't search for the file") }

            // Expose resources on the '/public' resource folder over the '/web' HTTP path
            on(
                status = NOT_FOUND_404,
                pattern = "/web/*",
                callback = UrlCallback(urlOf("classpath:public"))
            )

            // Maps resources on 'assets' on the server root (assets/f.css -> /f.css)
            // '/public/css/style.css' resource would be: 'http://{host}:{port}/css/style.css'
            on(status = NOT_FOUND_404, pattern = "/*", callback = UrlCallback(urlOf("classpath:assets")))
            // files
        }

        server.use { s ->
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()

                assert(it.get("/web/file.txt").bodyString().startsWith("It matches this route"))

                val index = it.get("/index.html")
                assertEquals(OK_200, index.status)
                assertEquals(ContentType(TEXT_HTML), index.contentType)
                val file = it.get("/web/file.css")
                assertEquals(OK_200, file.status)
                assertEquals(ContentType(TEXT_CSS), file.contentType)

                val unavailable = it.get("/web/unavailable.css")
                assertEquals(NOT_FOUND_404, unavailable.status)
            }
        }
    }

    @Test fun test() {
        // test
        val router = path {
            get("/hello") { ok("Hi!") }
        }

        val bindAddress = InetAddress.getLoopbackAddress()
        val serverSettings = HttpServerSettings(bindAddress, 0, banner = "name")
        val server = serve(serverAdapter(), router, serverSettings)

        server.use { s ->
            HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use {
                it.start()
                assertEquals("Hi!", it.get("/hello").body)
            }
        }
        // test
    }

    @Test fun mockRequest() {
        // mockRequest
        // Test callback (basically, a handler without a predicate)
        val callback: HttpCallbackType = {
            val fakeAttribute = attributes["fake"]
            val fakeHeader = request.headers["fake"]?.value
            ok("Callback result $fakeAttribute $fakeHeader")
        }

        // You can test callbacks with fake data
        val resultContext = callback.process(
            attributes = mapOf("fake" to "attribute"),
            headers = Headers(Header("fake", "header"))
        )

        assertEquals("Callback result attribute header", resultContext.response.bodyString())

        // Handlers can also be tested to check predicates along the callbacks
        val handler = Get("/path", callback)

        val notFound = handler.process(HttpRequest())
        val ok = handler.process(HttpRequest(method = GET, path = "/path"))

        assertEquals(NOT_FOUND_404, notFound.status)
        assertEquals(OK_200, ok.status)
        assertEquals("Callback result null null", ok.response.bodyString())
        // mockRequest
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy