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

io.javalin.http.servlet.JavalinServlet.kt Maven / Gradle / Ivy

The newest version!
/*
 * Javalin - https://javalin.io
 * Copyright 2017 David Åse
 * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
 */

package io.javalin.http.servlet

import io.javalin.config.JavalinConfig
import io.javalin.http.Context
import io.javalin.http.HttpStatus.INTERNAL_SERVER_ERROR
import io.javalin.http.HttpStatus.REQUEST_TIMEOUT
import io.javalin.http.servlet.SubmitOrder.FIRST
import io.javalin.http.servlet.SubmitOrder.LAST
import io.javalin.http.util.AsyncUtil.isAsync
import io.javalin.http.util.AsyncUtil.newAsyncListener
import io.javalin.http.util.ETagGenerator
import io.javalin.util.javalinLazy
import jakarta.servlet.http.HttpServlet
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse

class JavalinServlet(val cfg: JavalinConfig) : HttpServlet() {

    val requestLifecycle = cfg.pvt.servletRequestLifecycle.toList()
    val router = cfg.pvt.internalRouter
    private val servletContextConfig by javalinLazy { JavalinServletContextConfig.of(cfg) }

    override fun service(request: HttpServletRequest, response: HttpServletResponse) {
        handle(JavalinServletRequest(request), response)
    }

    fun handle(request: HttpServletRequest, response: HttpServletResponse): Context? {
        try {
            val ctx = JavalinServletContext(
                cfg = servletContextConfig,
                req = request,
                res = response
            )

            val submitTask: (SubmitOrder, Task) -> Unit = { order, task ->
                when (order) {
                    FIRST -> ctx.tasks.offerFirst(task)
                    LAST -> ctx.tasks.add(task)
                }
            }
            val requestUri = ctx.path().removePrefix(ctx.contextPath())
            requestLifecycle.forEach { it.createTasks(submitTask, this, ctx, requestUri) }

            ctx.handleSync()
            return ctx
        } catch (throwable: Throwable) {
            router.handleHttpUnexpectedThrowable(response, throwable)
            return null
        }
    }

    private fun JavalinServletContext.handleSync() {
        while (userFutureSupplier == null && tasks.isNotEmpty()) {
            val task = tasks.poll()
            if (exceptionOccurred && task.skipIfExceptionOccurred) {
                continue
            }
            handleTask(task.handler)
        }
        when {
            userFutureSupplier != null -> handleUserFuture()
            else -> writeResponseAndLog()
        }
    }

    private fun JavalinServletContext.handleUserFuture() {
        val userFutureSupplier = userFutureSupplier!!.also { userFutureSupplier = null } // nullcheck in handleSync
        if (!isAsync()) startAsyncAndAddDefaultTimeoutListeners() // start async if not already started

        val userFuture = handleTask { userFutureSupplier.get() } ?: return handleSync() // get future from supplier or handle error
        req().asyncContext.addListener(newAsyncListener(onTimeout = { userFuture.cancel(true) })) // cancel user's future if timeout occurs

        userFuture
            .thenApply { handleSync() }
            .exceptionally {
                router.handleHttpException(this, it)
                writeResponseAndLog()
            }
    }

    private fun JavalinServletContext.startAsyncAndAddDefaultTimeoutListeners() = req().startAsync().also {
        it.timeout = cfg.http.asyncTimeout
        it.addListener(newAsyncListener(onTimeout = { // a timeout avoids the pipeline - we need to handle it manually + it's not thread-safe
            status(INTERNAL_SERVER_ERROR) // default error handling
            router.handleHttpError(statusCode(), this) // user defined error handling
            if (resultInputStream() == null) result(REQUEST_TIMEOUT.message) // write default response only if handler didn't do anything
            writeResponseAndLog()
        }))
    }

    private fun  JavalinServletContext.handleTask(handler: TaskHandler): R? =
        try {
            handler.handle()
        } catch (throwable: Throwable) {
            exceptionOccurred = true
            userFutureSupplier = null
            tasks.offerFirst(Task(skipIfExceptionOccurred = false) { router.handleHttpException(this, throwable) })
            null
        }

    private fun JavalinServletContext.writeResponseAndLog() {
        try {
            if (responseWritten.getAndSet(true)) return // prevent writing more than once, it's required because timeout listener can terminate the flow at any time
            resultInputStream()?.use { resultStream ->
                val etagWritten = ETagGenerator.tryWriteEtagAndClose(cfg.http.generateEtags, this, resultStream)
                if (!etagWritten) resultStream.copyTo(outputStream(), 4096)
            }
            cfg.pvt.requestLogger?.handle(this, executionTimeMs())
        } catch (throwable: Throwable) {
            router.handleHttpUnexpectedThrowable(res(), throwable) // handle any unexpected error, e.g. write failure
        } finally {
            if (outputStreamWrapper.isInitialized()) outputStream().close() // close initialized output wrappers
            if (isAsync()) req().asyncContext.complete() // guarantee completion of async context to eliminate the possibility of hanging connections
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy