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

com.fireflysource.net.http.client.impl.Http2ClientConnection.kt Maven / Gradle / Ivy

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

import com.fireflysource.common.concurrent.exceptionallyAccept
import com.fireflysource.common.io.BufferUtils
import com.fireflysource.common.io.flipToFill
import com.fireflysource.common.io.flipToFlush
import com.fireflysource.common.io.useAwait
import com.fireflysource.common.sys.Result.discard
import com.fireflysource.common.sys.SystemLogger
import com.fireflysource.net.http.client.HttpClientConnection
import com.fireflysource.net.http.client.HttpClientContentProvider
import com.fireflysource.net.http.client.HttpClientRequest
import com.fireflysource.net.http.client.HttpClientResponse
import com.fireflysource.net.http.common.HttpConfig
import com.fireflysource.net.http.common.HttpConfig.DEFAULT_WINDOW_SIZE
import com.fireflysource.net.http.common.model.MetaData
import com.fireflysource.net.http.common.v2.decoder.Parser
import com.fireflysource.net.http.common.v2.frame.*
import com.fireflysource.net.http.common.v2.stream.*
import com.fireflysource.net.tcp.TcpConnection
import com.fireflysource.net.tcp.aio.AdaptiveBufferSize
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await
import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.function.UnaryOperator

class Http2ClientConnection(
    config: HttpConfig,
    tcpConnection: TcpConnection,
    flowControl: FlowControl = BufferedFlowControlStrategy(),
    listener: Http2Connection.Listener = defaultHttp2ConnectionListener,
    priorKnowledge: Boolean = true
) : AsyncHttp2Connection(1, config, tcpConnection, flowControl, listener), HttpClientConnection {

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

    private val parser: Parser = Parser(this, config.maxDynamicTableSize, config.maxHeaderSize)
    private val adaptiveBufferSize = AdaptiveBufferSize()

    init {
        if (priorKnowledge) {
            sendConnectionPreface()
            parser.init(UnaryOperator.identity())
            launchParserJob(parser)
        }
    }

    fun upgradeHttp2(request: HttpClientRequest, frameBytes: ByteBuffer?): CompletableFuture {
        val streamId = getNextStreamId()
        val future = CompletableFuture()
        val streamListener = Http2ClientStreamListener(request, future)
        createLocalStream(streamId, streamListener)
        sendConnectionPreface()
        parser.init(UnaryOperator.identity())
        if (frameBytes != null) {
            log.debug { "Upgrade HTTP2 and received frame data. id: $id, remaining: ${frameBytes.remaining()}" }
            while (frameBytes.hasRemaining()) {
                parser.parse(frameBytes)
            }
        }
        launchParserJob(parser)
        return future
    }

    private fun sendConnectionPreface() {
        val settings = notifyPreface()

        val maxFrameLength = settings[SettingsFrame.MAX_FRAME_SIZE]
        if (maxFrameLength != null) {
            parser.maxFrameLength = maxFrameLength
        }

        val prefaceFrame = PrefaceFrame()
        val settingsFrame = SettingsFrame(settings, false)
        val windowDelta = initialSessionRecvWindow - DEFAULT_WINDOW_SIZE
        if (windowDelta > 0) {
            val windowUpdateFrame = WindowUpdateFrame(0, windowDelta)
            updateRecvWindow(windowDelta)
            sendControlFrame(null, prefaceFrame, settingsFrame, windowUpdateFrame)
                .thenAccept { log.info { "send connection preface success. id: $id, settings: $settingsFrame" } }
                .exceptionallyAccept { log.error(it) { "send connection preface exception. id: $id" } }
        } else {
            sendControlFrame(null, prefaceFrame, settingsFrame)
                .thenAccept { log.info { "send connection preface success. id: $id, settings: $settingsFrame" } }
                .exceptionallyAccept { log.error(it) { "send connection preface exception. id: $id" } }
        }
    }

    override fun onHeaders(frame: HeadersFrame) {
        log.debug { "Received $frame" }

        // HEADERS can be received for normal and pushed responses.
        val streamId = frame.streamId
        val stream = getStream(streamId)
        if (stream != null && stream is AsyncHttp2Stream) {
            val metaData = frame.metaData
            if (metaData.isRequest) {
                onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_response")
            } else {
                stream.process(frame, discard())
                notifyHeaders(stream, frame)
            }
        } else {
            log.debug { "Stream: $streamId not found" }
            if (isClientStream(streamId)) {
                // The normal stream. Headers or trailers arriving after the stream has been reset are ignored.
                if (!isLocalStreamClosed(streamId)) {
                    onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame")
                }
            } else {
                // The pushed stream. Headers or trailers arriving after the stream has been reset are ignored.
                if (!isRemoteStreamClosed(streamId)) {
                    onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame")
                }
            }
        }
    }


    // promise frame
    override fun onPushPromise(frame: PushPromiseFrame) {
        log.debug { "Received $frame" }

        val stream = getStream(frame.streamId)
        if (stream == null) {
            log.debug { "Ignoring $frame, stream: ${frame.streamId} not found" }
        } else {
            val pushStream = createRemoteStream(frame.promisedStreamId)
            if (pushStream != null && pushStream is AsyncHttp2Stream) {
                pushStream.process(frame, discard())
                pushStream.listener = notifyPush(stream, pushStream, frame)
            }
        }
    }

    private fun notifyPush(stream: Stream, pushStream: Stream, frame: PushPromiseFrame): Stream.Listener {
        return try {
            val listener = (stream as AsyncHttp2Stream).listener
            listener.onPush(pushStream, frame)
        } catch (e: Exception) {
            log.error(e) { "failure while notifying listener" }
            AsyncHttp2Stream.defaultStreamListener
        }
    }

    override fun onResetForUnknownStream(frame: ResetFrame) {
        val streamId = frame.streamId
        val closed = if (isClientStream(streamId)) isLocalStreamClosed(streamId) else isRemoteStreamClosed(streamId)
        if (closed) {
            notifyReset(this, frame)
        } else {
            onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame")
        }
    }

    override fun send(request: HttpClientRequest): CompletableFuture {
        val metaDataRequest: MetaData.Request = toMetaDataRequest(request)
        val contentProvider: HttpClientContentProvider? = request.contentProvider
        val lastHeaders = contentProvider == null && metaDataRequest.trailerSupplier == null
        val headersFrame = HeadersFrame(metaDataRequest, null, lastHeaders)

        val future = CompletableFuture()
        val streamListener = Http2ClientStreamListener(request, future)
        val serverAccepted = streamListener.serverAccepted
        newStream(headersFrame, streamListener)
            .thenCompose { newStream -> serverAccepted.thenApply { Pair(newStream, it) } }
            .thenCompose { generateContent(contentProvider, metaDataRequest, it.first, it.second) }
            .thenAccept { generateTrailer(metaDataRequest, it.first, it.second) }
            .exceptionallyAccept {
                log.error(it) { "The HTTP2 client connection creates local stream failure. id: $id " }
                future.completeExceptionally(it)
            }
        return future
    }

    private fun generateContent(
        contentProvider: HttpClientContentProvider?,
        metaDataRequest: MetaData.Request,
        newStream: Stream,
        serverAccept: Boolean
    ) = tcpConnection.coroutineScope.async {
        if (contentProvider != null && serverAccept) {
            contentProvider.useAwait {
                val byteBuffers = LinkedList()
                readLoop@ while (true) {
                    val contentBuffer = BufferUtils.allocate(adaptiveBufferSize.getBufferSize())
                    val pos = contentBuffer.flipToFill()
                    val length = contentProvider.read(contentBuffer).await()
                    contentBuffer.flipToFlush(pos)
                    adaptiveBufferSize.update(length)

                    when {
                        length > 0 -> byteBuffers.offer(contentBuffer)
                        length < 0 -> break@readLoop
                    }

                    if (byteBuffers.size > 1) {
                        val dataFrame = DataFrame(newStream.id, byteBuffers.poll(), false)
                        newStream.data(dataFrame)
                    }
                }

                val last = metaDataRequest.trailerSupplier == null
                if (byteBuffers.isNotEmpty()) {
                    val dataFrame = DataFrame(newStream.id, byteBuffers.poll(), last)
                    newStream.data(dataFrame)
                } else {
                    val empty = ByteBuffer.allocate(0)
                    val dataFrame = DataFrame(newStream.id, empty, last)
                    newStream.data(dataFrame)
                }
            }
        }
        Pair(newStream, serverAccept)
    }.asCompletableFuture()

    private fun generateTrailer(metaDataRequest: MetaData.Request, newStream: Stream, serverAccept: Boolean) {
        val trailerSupplier = metaDataRequest.trailerSupplier
        if (trailerSupplier != null && serverAccept) {
            val trailerMetaData = MetaData.Request(trailerSupplier.get())
            trailerMetaData.isOnlyTrailer = true
            val headersFrameTrailer = HeadersFrame(newStream.id, trailerMetaData, null, true)
            newStream.headers(headersFrameTrailer, discard())
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy