//===----------------------------------------------------------------------===// // // 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 NIOCore import NIOHTTP1 import NIOHTTPTypes /// A simple channel handler that translates shared HTTP types into HTTP/1 messages, /// and vice versa, for use on the client side. /// /// This is intended for compatibility purposes where a channel handler working with /// HTTP/1 messages needs to work on top of the new version-independent HTTP types /// abstraction. public final class HTTPToHTTP1ClientCodec: ChannelDuplexHandler, RemovableChannelHandler { public typealias InboundIn = HTTPResponsePart public typealias InboundOut = HTTPClientResponsePart public typealias OutboundIn = HTTPClientRequestPart public typealias OutboundOut = HTTPRequestPart private let secure: Bool private let splitCookie: Bool /// Initializes a `HTTPToHTTP1ClientCodec`. /// - Parameters: /// - secure: Whether "https" or "http" is used. /// - splitCookie: Whether the cookies sent by the client should be split /// into multiple header fields. Splitting the `Cookie` /// header field improves the performance of HTTP/2 and /// HTTP/3 clients by allowing individual cookies to be /// indexed separately in the dynamic table. It has no /// effects in HTTP/1. Defaults to true. public init(secure: Bool, splitCookie: Bool = true) { self.secure = secure self.splitCookie = splitCookie } public func channelRead(context: ChannelHandlerContext, data: NIOAny) { switch self.unwrapInboundIn(data) { case .head(let head): let oldResponse = HTTPResponseHead(head) context.fireChannelRead(self.wrapInboundOut(.head(oldResponse))) case .body(let body): context.fireChannelRead(self.wrapInboundOut(.body(body))) case .end(let trailers): context.fireChannelRead(self.wrapInboundOut(.end(trailers.map(HTTPHeaders.init)))) } } public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { switch self.unwrapOutboundIn(data) { case .head(let request): do { let newRequest = try HTTPRequest(request, secure: self.secure, splitCookie: self.splitCookie) context.write(self.wrapOutboundOut(.head(newRequest)), promise: promise) } catch { context.fireErrorCaught(error) promise?.fail(error) } case .body(.byteBuffer(let body)): context.write(self.wrapOutboundOut(.body(body)), promise: promise) case .body: fatalError("File region not supported") case .end(let trailers): let newTrailers = trailers.map { HTTPFields($0, splitCookie: false) } context.write(self.wrapOutboundOut(.end(newTrailers)), promise: promise) } } } /// A simple channel handler that translates shared HTTP types into HTTP/1 messages, /// and vice versa, for use on the server side. /// /// This is intended for compatibility purposes where a channel handler working with /// HTTP/1 messages needs to work on top of the new version-independent HTTP types /// abstraction. public final class HTTPToHTTP1ServerCodec: ChannelDuplexHandler, RemovableChannelHandler { public typealias InboundIn = HTTPRequestPart public typealias InboundOut = HTTPServerRequestPart public typealias OutboundIn = HTTPServerResponsePart public typealias OutboundOut = HTTPResponsePart /// Initializes a `HTTPToHTTP1ServerCodec`. public init() {} public func channelRead(context: ChannelHandlerContext, data: NIOAny) { switch self.unwrapInboundIn(data) { case .head(let head): do { let oldRequest = try HTTPRequestHead(head) context.fireChannelRead(self.wrapInboundOut(.head(oldRequest))) } catch { context.fireErrorCaught(error) } case .body(let body): context.fireChannelRead(self.wrapInboundOut(.body(body))) case .end(let trailers): context.fireChannelRead(self.wrapInboundOut(.end(trailers.map(HTTPHeaders.init)))) } } public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { switch self.unwrapOutboundIn(data) { case .head(let response): do { let newResponse = try HTTPResponse(response) context.write(self.wrapOutboundOut(.head(newResponse)), promise: promise) } catch { context.fireErrorCaught(error) promise?.fail(error) } case .body(.byteBuffer(let body)): context.write(self.wrapOutboundOut(.body(body)), promise: promise) case .body: fatalError("File region not supported") case .end(let trailers): let newTrailers = trailers.map { HTTPFields($0, splitCookie: false) } context.write(self.wrapOutboundOut(.end(newTrailers)), promise: promise) } } }