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

com.fireflysource.net.http.server.impl.Http1ServerConnection.kt Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
package com.fireflysource.net.http.server.impl

import com.fireflysource.common.`object`.Assert
import com.fireflysource.common.sys.SystemLogger
import com.fireflysource.net.Connection
import com.fireflysource.net.http.common.HttpConfig
import com.fireflysource.net.http.common.TcpBasedHttpConnection
import com.fireflysource.net.http.common.exception.BadMessageException
import com.fireflysource.net.http.common.exception.HttpServerConnectionListenerNotSetException
import com.fireflysource.net.http.common.model.HttpVersion
import com.fireflysource.net.http.common.v1.decoder.HttpParser
import com.fireflysource.net.http.common.v1.decoder.parseAll
import com.fireflysource.net.http.server.HttpServerConnection
import com.fireflysource.net.tcp.TcpConnection
import com.fireflysource.net.tcp.TcpCoroutineDispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import java.io.IOException
import java.util.concurrent.CancellationException
import java.util.concurrent.atomic.AtomicBoolean

class Http1ServerConnection(
    val config: HttpConfig,
    private val tcpConnection: TcpConnection
) : Connection by tcpConnection, TcpCoroutineDispatcher by tcpConnection, TcpBasedHttpConnection, HttpServerConnection {

    companion object {
        private val log = SystemLogger.create(Http1ServerConnection::class.java)
    }

    private val requestHandler = Http1ServerRequestHandler(this)
    private val parser = HttpParser(requestHandler)
    private val responseHandler = Http1ServerResponseHandler(this)
    private val beginning = AtomicBoolean(false)
    private val channel: Channel = Channel(Channel.UNLIMITED)
    private var parseRequestJob: Job? = null
    private var generateResponseJob: Job? = null

    private fun parseRequestJob() = coroutineScope.launch {
        parseLoop@ while (true) {
            when (channel.receive()) {
                is ParseNextHttpPacket -> parseNextHttpPacket()
                is ExitHttpParser -> {
                    log.info { "Exit the HTTP server parser. id: $id" }
                    break@parseLoop
                }
            }
        }
    }

    private suspend fun parseNextHttpPacket() {
        try {
            parser.parseAll(tcpConnection)
        } catch (e: BadMessageException) {
            requestHandler.badMessage(e)
            channel.offer(ExitHttpParser)
        } catch (e: IOException) {
            log.info { "The TCP connection IO exception. message: ${e.message ?: e.javaClass.name}, id: $id" }
            channel.offer(ExitHttpParser)
        } catch (e: CancellationException) {
            log.info { "Cancel HTTP1 parsing. message: ${e.message} id: $id" }
            channel.offer(ExitHttpParser)
        } catch (e: Exception) {
            log.error(e) { "Parse HTTP1 request exception. id: $id" }
        } finally {
            resetParser()
        }
    }

    fun parseNextRequest() {
        channel.offer(ParseNextHttpPacket)
    }

    suspend fun endHttpParser() {
        channel.offer(ExitHttpParser)
        parseRequestJob?.join()
    }

    fun resetParser() {
        parser.reset()
    }

    private fun generateResponseJob() = responseHandler.generateResponseJob()

    fun getHeaderBufferSize() = config.headerBufferSize

    fun sendResponseMessage(message: Http1ResponseMessage) = responseHandler.sendResponseMessage(message)

    suspend fun endResponseHandler() {
        responseHandler.endResponseHandler()
        generateResponseJob?.join()
    }

    override fun begin() {
        if (beginning.compareAndSet(false, true)) {
            if (requestHandler.connectionListener === HttpServerConnection.EMPTY_LISTENER) {
                throw HttpServerConnectionListenerNotSetException("Please set connection listener before begin parsing.")
            }
            parseRequestJob = parseRequestJob()
            generateResponseJob = generateResponseJob()
            parseNextRequest()
        }
    }

    override fun setListener(listener: HttpServerConnection.Listener): HttpServerConnection {
        Assert.state(
            !beginning.get(),
            "The HTTP request parser has started. Please set listener before begin parsing."
        )
        requestHandler.connectionListener = listener
        return this
    }

    override fun getHttpVersion(): HttpVersion = HttpVersion.HTTP_1_1

    override fun isSecureConnection(): Boolean = tcpConnection.isSecureConnection

    override fun getTcpConnection(): TcpConnection = tcpConnection
}

sealed class ParseHttpPacketMessage
object ParseNextHttpPacket : ParseHttpPacketMessage()
object ExitHttpParser : ParseHttpPacketMessage()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy