Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions ktor-http/common/src/io/ktor/http/HttpProtocolVersion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

// ABOUTME: Represents HTTP protocol version (e.g., HTTP/1.1, HTTP/2.0).
// ABOUTME: Provides cached instances for common versions and zero-allocation parsing for HTTP protocol.

package io.ktor.http

/**
Expand Down Expand Up @@ -77,16 +80,59 @@ public data class HttpProtocolVersion(val name: String, val major: Int, val mino
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.HttpProtocolVersion.Companion.parse)
*/
public fun parse(value: CharSequence): HttpProtocolVersion {
/**
* Format: protocol/major.minor
*/
val (protocol, major, minor) = value.split("/", ".").also {
check(it.size == 3) {
// Format: protocol/major.minor
// Zero-allocation fast path for common HTTP versions
val slashIndex = value.indexOf('/')
val dotIndex = if (slashIndex >= 0) value.indexOf('.', slashIndex + 1) else -1

if (slashIndex < 0 || dotIndex < 0) {
throw IllegalArgumentException(
"Failed to parse HttpProtocolVersion. Expected format: protocol/major.minor, but actual: $value"
)
}

val major = parseDigits(value, slashIndex + 1, dotIndex)
val minor = parseDigits(value, dotIndex + 1, value.length)

if (major < 0 || minor < 0) {
throw IllegalArgumentException(
"Failed to parse HttpProtocolVersion. Expected format: protocol/major.minor, but actual: $value"
)
}

// Fast path for HTTP (most common) - returns cached instances, no allocation
if (isHttp(value, slashIndex)) {
return when {
major == 1 && minor == 0 -> HTTP_1_0
major == 1 && minor == 1 -> HTTP_1_1
major == 2 && minor == 0 -> HTTP_2_0
major == 3 && minor == 0 -> HTTP_3_0
else -> HttpProtocolVersion("HTTP", major, minor)
}
}

return fromValue(protocol, major.toInt(), minor.toInt())
// Slow path for other protocols - needs string allocation
val protocol = value.substring(0, slashIndex)
return fromValue(protocol, major, minor)
}

private fun isHttp(value: CharSequence, slashIndex: Int): Boolean {
return slashIndex == 4 &&
value[0] == 'H' &&
value[1] == 'T' &&
value[2] == 'T' &&
value[3] == 'P'
}

private fun parseDigits(value: CharSequence, start: Int, end: Int): Int {
if (start >= end) return -1
var result = 0
for (i in start until end) {
val c = value[i]
if (c !in '0'..'9') return -1
result = result * 10 + (c - '0')
}
return result
}
}

Expand Down
6 changes: 6 additions & 0 deletions ktor-io/api/ktor-io.api
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,11 @@ public final class io/ktor/utils/io/jvm/javaio/WritingKt {
public static synthetic fun copyTo$default (Lio/ktor/utils/io/ByteReadChannel;Ljava/io/OutputStream;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public final class io/ktor/utils/io/jvm/nio/ByteBufferHolder {
public fun <init> ()V
public final fun getOrCreate ([BII)Ljava/nio/ByteBuffer;
}

public final class io/ktor/utils/io/jvm/nio/ReadingKt {
public static final fun asSource (Ljava/nio/channels/ReadableByteChannel;)Lkotlinx/io/RawSource;
public static final fun toByteReadChannel (Ljava/nio/channels/ReadableByteChannel;Lkotlin/coroutines/CoroutineContext;)Lio/ktor/utils/io/ByteReadChannel;
Expand All @@ -609,6 +614,7 @@ public final class io/ktor/utils/io/jvm/nio/WriteSuspendSession {
}

public final class io/ktor/utils/io/jvm/nio/WriteSuspendSessionKt {
public static final fun getThreadLocalByteBufferHolder ()Ljava/lang/ThreadLocal;
public static final fun writeSuspendSession (Lio/ktor/utils/io/ByteWriteChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun writeWhile (Lio/ktor/utils/io/ByteWriteChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
Expand Down
Loading