Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.fireflysource.net.http.client.impl.Http2ClientConnection.kt Maven / Gradle / Ivy
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())
}
}
}