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

main.okhttp3.internal.http2.Http2Writer.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.internal.http2

import java.io.Closeable
import java.io.IOException
import java.util.logging.Level.FINE
import java.util.logging.Logger
import okhttp3.internal.format
import okhttp3.internal.http2.Http2.CONNECTION_PREFACE
import okhttp3.internal.http2.Http2.FLAG_ACK
import okhttp3.internal.http2.Http2.FLAG_END_HEADERS
import okhttp3.internal.http2.Http2.FLAG_END_STREAM
import okhttp3.internal.http2.Http2.FLAG_NONE
import okhttp3.internal.http2.Http2.INITIAL_MAX_FRAME_SIZE
import okhttp3.internal.http2.Http2.TYPE_CONTINUATION
import okhttp3.internal.http2.Http2.TYPE_DATA
import okhttp3.internal.http2.Http2.TYPE_GOAWAY
import okhttp3.internal.http2.Http2.TYPE_HEADERS
import okhttp3.internal.http2.Http2.TYPE_PING
import okhttp3.internal.http2.Http2.TYPE_PUSH_PROMISE
import okhttp3.internal.http2.Http2.TYPE_RST_STREAM
import okhttp3.internal.http2.Http2.TYPE_SETTINGS
import okhttp3.internal.http2.Http2.TYPE_WINDOW_UPDATE
import okhttp3.internal.http2.Http2.frameLog
import okhttp3.internal.http2.Http2.frameLogWindowUpdate
import okhttp3.internal.writeMedium
import okio.Buffer
import okio.BufferedSink

/** Writes HTTP/2 transport frames. */
@Suppress("NAME_SHADOWING")
class Http2Writer(
  private val sink: BufferedSink,
  private val client: Boolean,
) : Closeable {
  private val hpackBuffer: Buffer = Buffer()
  private var maxFrameSize: Int = INITIAL_MAX_FRAME_SIZE
  private var closed: Boolean = false
  val hpackWriter: Hpack.Writer = Hpack.Writer(out = hpackBuffer)

  @Synchronized
  @Throws(IOException::class)
  fun connectionPreface() {
    if (closed) throw IOException("closed")
    if (!client) return // Nothing to write; servers don't send connection headers!
    if (logger.isLoggable(FINE)) {
      logger.fine(format(">> CONNECTION ${CONNECTION_PREFACE.hex()}"))
    }
    sink.write(CONNECTION_PREFACE)
    sink.flush()
  }

  /** Applies `peerSettings` and then sends a settings ACK. */
  @Synchronized
  @Throws(IOException::class)
  fun applyAndAckSettings(peerSettings: Settings) {
    if (closed) throw IOException("closed")
    this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize)
    if (peerSettings.headerTableSize != -1) {
      hpackWriter.resizeHeaderTable(peerSettings.headerTableSize)
    }
    frameHeader(
      streamId = 0,
      length = 0,
      type = TYPE_SETTINGS,
      flags = FLAG_ACK,
    )
    sink.flush()
  }

  /**
   * HTTP/2 only. Send a push promise header block.
   *
   * A push promise contains all the headers that pertain to a server-initiated request, and a
   * `promisedStreamId` to which response frames will be delivered. Push promise frames are sent as
   * a part of the response to `streamId`. The `promisedStreamId` has a priority of one greater than
   * `streamId`.
   *
   * @param streamId client-initiated stream ID.  Must be an odd number.
   * @param promisedStreamId server-initiated stream ID.  Must be an even number.
   * @param requestHeaders minimally includes `:method`, `:scheme`, `:authority`, and `:path`.
   */
  @Synchronized
  @Throws(IOException::class)
  fun pushPromise(
    streamId: Int,
    promisedStreamId: Int,
    requestHeaders: List
, ) { if (closed) throw IOException("closed") hpackWriter.writeHeaders(requestHeaders) val byteCount = hpackBuffer.size val length = minOf(maxFrameSize - 4L, byteCount).toInt() frameHeader( streamId = streamId, length = length + 4, type = TYPE_PUSH_PROMISE, flags = if (byteCount == length.toLong()) FLAG_END_HEADERS else 0, ) sink.writeInt(promisedStreamId and 0x7fffffff) sink.write(hpackBuffer, length.toLong()) if (byteCount > length) writeContinuationFrames(streamId, byteCount - length) } @Synchronized @Throws(IOException::class) fun flush() { if (closed) throw IOException("closed") sink.flush() } @Synchronized @Throws(IOException::class) fun rstStream( streamId: Int, errorCode: ErrorCode, ) { if (closed) throw IOException("closed") require(errorCode.httpCode != -1) frameHeader( streamId = streamId, length = 4, type = TYPE_RST_STREAM, flags = FLAG_NONE, ) sink.writeInt(errorCode.httpCode) sink.flush() } /** The maximum size of bytes that may be sent in a single call to [data]. */ fun maxDataLength(): Int = maxFrameSize /** * `source.length` may be longer than the max length of the variant's data frame. Implementations * must send multiple frames as necessary. * * @param source the buffer to draw bytes from. May be null if byteCount is 0. * @param byteCount must be between 0 and the minimum of `source.length` and [maxDataLength]. */ @Synchronized @Throws(IOException::class) fun data( outFinished: Boolean, streamId: Int, source: Buffer?, byteCount: Int, ) { if (closed) throw IOException("closed") var flags = FLAG_NONE if (outFinished) flags = flags or FLAG_END_STREAM dataFrame(streamId, flags, source, byteCount) } @Throws(IOException::class) fun dataFrame( streamId: Int, flags: Int, buffer: Buffer?, byteCount: Int, ) { frameHeader( streamId = streamId, length = byteCount, type = TYPE_DATA, flags = flags, ) if (byteCount > 0) { sink.write(buffer!!, byteCount.toLong()) } } /** Write okhttp's settings to the peer. */ @Synchronized @Throws(IOException::class) fun settings(settings: Settings) { if (closed) throw IOException("closed") frameHeader( streamId = 0, length = settings.size() * 6, type = TYPE_SETTINGS, flags = FLAG_NONE, ) for (i in 0 until Settings.COUNT) { if (!settings.isSet(i)) continue val id = when (i) { 4 -> 3 // SETTINGS_MAX_CONCURRENT_STREAMS renumbered. 7 -> 4 // SETTINGS_INITIAL_WINDOW_SIZE renumbered. else -> i } sink.writeShort(id) sink.writeInt(settings[i]) } sink.flush() } /** * Send a connection-level ping to the peer. `ack` indicates this is a reply. The data in * `payload1` and `payload2` opaque binary, and there are no rules on the content. */ @Synchronized @Throws(IOException::class) fun ping( ack: Boolean, payload1: Int, payload2: Int, ) { if (closed) throw IOException("closed") frameHeader( streamId = 0, length = 8, type = TYPE_PING, flags = if (ack) FLAG_ACK else FLAG_NONE, ) sink.writeInt(payload1) sink.writeInt(payload2) sink.flush() } /** * Tell the peer to stop creating streams and that we last processed `lastGoodStreamId`, or zero * if no streams were processed. * * @param lastGoodStreamId the last stream ID processed, or zero if no streams were processed. * @param errorCode reason for closing the connection. * @param debugData only valid for HTTP/2; opaque debug data to send. */ @Synchronized @Throws(IOException::class) fun goAway( lastGoodStreamId: Int, errorCode: ErrorCode, debugData: ByteArray, ) { if (closed) throw IOException("closed") require(errorCode.httpCode != -1) { "errorCode.httpCode == -1" } frameHeader( streamId = 0, length = 8 + debugData.size, type = TYPE_GOAWAY, flags = FLAG_NONE, ) sink.writeInt(lastGoodStreamId) sink.writeInt(errorCode.httpCode) if (debugData.isNotEmpty()) { sink.write(debugData) } sink.flush() } /** * Inform peer that an additional `windowSizeIncrement` bytes can be sent on `streamId`, or the * connection if `streamId` is zero. */ @Synchronized @Throws(IOException::class) fun windowUpdate( streamId: Int, windowSizeIncrement: Long, ) { if (closed) throw IOException("closed") require(windowSizeIncrement != 0L && windowSizeIncrement <= 0x7fffffffL) { "windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: $windowSizeIncrement" } if (logger.isLoggable(FINE)) { logger.fine( frameLogWindowUpdate( inbound = false, streamId = streamId, length = 4, windowSizeIncrement = windowSizeIncrement, ), ) } frameHeader( streamId = streamId, length = 4, type = TYPE_WINDOW_UPDATE, flags = FLAG_NONE, ) sink.writeInt(windowSizeIncrement.toInt()) sink.flush() } @Throws(IOException::class) fun frameHeader( streamId: Int, length: Int, type: Int, flags: Int, ) { if (type != TYPE_WINDOW_UPDATE && logger.isLoggable(FINE)) { logger.fine(frameLog(false, streamId, length, type, flags)) } require(length <= maxFrameSize) { "FRAME_SIZE_ERROR length > $maxFrameSize: $length" } require(streamId and 0x80000000.toInt() == 0) { "reserved bit set: $streamId" } sink.writeMedium(length) sink.writeByte(type and 0xff) sink.writeByte(flags and 0xff) sink.writeInt(streamId and 0x7fffffff) } @Synchronized @Throws(IOException::class) override fun close() { closed = true sink.close() } @Throws(IOException::class) private fun writeContinuationFrames( streamId: Int, byteCount: Long, ) { var byteCount = byteCount while (byteCount > 0L) { val length = minOf(maxFrameSize.toLong(), byteCount) byteCount -= length frameHeader( streamId = streamId, length = length.toInt(), type = TYPE_CONTINUATION, flags = if (byteCount == 0L) FLAG_END_HEADERS else 0, ) sink.write(hpackBuffer, length) } } @Synchronized @Throws(IOException::class) fun headers( outFinished: Boolean, streamId: Int, headerBlock: List
, ) { if (closed) throw IOException("closed") hpackWriter.writeHeaders(headerBlock) val byteCount = hpackBuffer.size val length = minOf(maxFrameSize.toLong(), byteCount) var flags = if (byteCount == length) FLAG_END_HEADERS else 0 if (outFinished) flags = flags or FLAG_END_STREAM frameHeader( streamId = streamId, length = length.toInt(), type = TYPE_HEADERS, flags = flags, ) sink.write(hpackBuffer, length) if (byteCount > length) writeContinuationFrames(streamId, byteCount - length) } companion object { private val logger = Logger.getLogger(Http2::class.java.name) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy