1
0
mirror of https://github.com/apple/swift-nio-extras.git synced 2025-05-17 02:22:18 +08:00
Guoye Zhang 798c962495
Add HTTP types adapter for SwiftNIO ()
* Add HTTP types adapter for SwiftNIO

* swiftformat

* Guard on Swift 5.8

* Review comments

* Update swift-http-types to 0.1.1

* Update swift-http-types to 1.0.0

* Review feedback

* Review feedback

* Bump minimum Swift version to 5.7.1

* Allow Host in any order
2023-10-20 17:55:23 +01:00

264 lines
8.5 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import HTTPTypes
import NIOHPACK
private enum HTTP2TypeConversionError: Error {
case multipleMethod
case multipleScheme
case multipleAuthority
case multiplePath
case multipleProtocol
case missingMethod
case invalidMethod
case multipleStatus
case missingStatus
case invalidStatus
case pseudoFieldNotFirst
case pseudoFieldInTrailers
}
private extension HPACKIndexing {
init(_ newIndexingStrategy: HTTPField.DynamicTableIndexingStrategy) {
switch newIndexingStrategy {
case .avoid: self = .nonIndexable
case .disallow: self = .neverIndexed
default: self = .indexable
}
}
}
private extension HTTPField.DynamicTableIndexingStrategy {
init(_ oldIndexing: HPACKIndexing) {
switch oldIndexing {
case .indexable: self = .automatic
case .nonIndexable: self = .avoid
case .neverIndexed: self = .disallow
}
}
}
extension HPACKHeaders {
private mutating func add(newField field: HTTPField) {
self.add(name: field.name.canonicalName, value: field.value, indexing: HPACKIndexing(field.indexingStrategy))
}
init(_ newRequest: HTTPRequest) {
self.init()
self.reserveCapacity(newRequest.headerFields.count + 5)
self.add(newField: newRequest.pseudoHeaderFields.method)
if let field = newRequest.pseudoHeaderFields.scheme {
self.add(newField: field)
}
if let field = newRequest.pseudoHeaderFields.authority {
self.add(newField: field)
}
if let field = newRequest.pseudoHeaderFields.path {
self.add(newField: field)
}
if let field = newRequest.pseudoHeaderFields.extendedConnectProtocol {
self.add(newField: field)
}
for field in newRequest.headerFields {
self.add(newField: field)
}
}
init(_ newResponse: HTTPResponse) {
self.init()
self.reserveCapacity(newResponse.headerFields.count + 1)
self.add(newField: newResponse.pseudoHeaderFields.status)
for field in newResponse.headerFields {
self.add(newField: field)
}
}
init(_ newTrailers: HTTPFields) {
self.init()
self.reserveCapacity(newTrailers.count)
for field in newTrailers {
self.add(newField: field)
}
}
}
extension HTTPRequest {
init(_ hpack: HPACKHeaders) throws {
var methodString: String? = nil
var methodIndexable: HPACKIndexing = .indexable
var schemeString: String? = nil
var schemeIndexable: HPACKIndexing = .indexable
var authorityString: String? = nil
var authorityIndexable: HPACKIndexing = .indexable
var pathString: String? = nil
var pathIndexable: HPACKIndexing = .indexable
var protocolString: String? = nil
var protocolIndexable: HPACKIndexing = .indexable
var i = hpack.startIndex
while i != hpack.endIndex {
let (name, value, indexable) = hpack[i]
if !name.hasPrefix(":") {
break
}
switch name {
case ":method":
if methodString != nil {
throw HTTP2TypeConversionError.multipleMethod
}
methodString = value
methodIndexable = indexable
case ":scheme":
if schemeString != nil {
throw HTTP2TypeConversionError.multipleScheme
}
schemeString = value
schemeIndexable = indexable
case ":authority":
if authorityString != nil {
throw HTTP2TypeConversionError.multipleAuthority
}
authorityString = value
authorityIndexable = indexable
case ":path":
if pathString != nil {
throw HTTP2TypeConversionError.multiplePath
}
pathString = value
pathIndexable = indexable
case ":protocol":
if protocolString != nil {
throw HTTP2TypeConversionError.multipleProtocol
}
protocolString = value
protocolIndexable = indexable
default:
continue
}
i = hpack.index(after: i)
}
guard let methodString else {
throw HTTP2TypeConversionError.missingMethod
}
guard let method = HTTPRequest.Method(methodString) else {
throw HTTP2TypeConversionError.invalidMethod
}
self.init(
method: method,
scheme: schemeString,
authority: authorityString,
path: pathString
)
self.pseudoHeaderFields.method.indexingStrategy = .init(methodIndexable)
self.pseudoHeaderFields.scheme?.indexingStrategy = .init(schemeIndexable)
self.pseudoHeaderFields.authority?.indexingStrategy = .init(authorityIndexable)
self.pseudoHeaderFields.path?.indexingStrategy = .init(pathIndexable)
if let protocolString {
self.extendedConnectProtocol = protocolString
self.pseudoHeaderFields.extendedConnectProtocol?.indexingStrategy = .init(protocolIndexable)
}
self.headerFields.reserveCapacity(hpack.count)
while i != hpack.endIndex {
let (name, value, indexable) = hpack[i]
if name.hasPrefix(":") {
throw HTTP2TypeConversionError.pseudoFieldNotFirst
}
if let fieldName = HTTPField.Name(name) {
var field = HTTPField(name: fieldName, value: value)
field.indexingStrategy = .init(indexable)
self.headerFields.append(field)
}
i = hpack.index(after: i)
}
}
}
extension HTTPResponse {
init(_ hpack: HPACKHeaders) throws {
var statusString: String? = nil
var statusIndexable: HPACKIndexing = .indexable
var i = hpack.startIndex
while i != hpack.endIndex {
let (name, value, indexable) = hpack[i]
if !name.hasPrefix(":") {
break
}
switch name {
case ":status":
if statusString != nil {
throw HTTP2TypeConversionError.multipleStatus
}
statusString = value
statusIndexable = indexable
default:
continue
}
i = hpack.index(after: i)
}
guard let statusString else {
throw HTTP2TypeConversionError.missingStatus
}
guard let status = Int(statusString),
(0 ... 999).contains(status) else {
throw HTTP2TypeConversionError.invalidStatus
}
self.init(status: HTTPResponse.Status(code: status))
self.pseudoHeaderFields.status.indexingStrategy = .init(statusIndexable)
self.headerFields.reserveCapacity(hpack.count)
while i != hpack.endIndex {
let (name, value, indexable) = hpack[i]
if name.hasPrefix(":") {
throw HTTP2TypeConversionError.pseudoFieldNotFirst
}
if let fieldName = HTTPField.Name(name) {
var field = HTTPField(name: fieldName, value: value)
field.indexingStrategy = .init(indexable)
self.headerFields.append(field)
}
i = hpack.index(after: i)
}
}
}
extension HTTPFields {
init(trailers: HPACKHeaders) throws {
self.init()
self.reserveCapacity(trailers.count)
for (name, value, indexable) in trailers {
if name.hasPrefix(":") {
throw HTTP2TypeConversionError.pseudoFieldInTrailers
}
if let fieldName = HTTPField.Name(name) {
var field = HTTPField(name: fieldName, value: value)
field.indexingStrategy = .init(indexable)
self.append(field)
}
}
}
}