Skip to content
Merged
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
86 changes: 80 additions & 6 deletions Sources/NIOHTTP1/HTTPEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ private func correctlyFrameTransportHeaders(hasBody: HTTPMethod.HasBody, headers
}
}

/// Validates the response/request headers to ensure that we correctly send the body with chunked transfer
/// encoding, when needed.
private func messageIsChunked(headers: HTTPHeaders, version: HTTPVersion) -> Bool {
if version.major == 1 && version.minor >= 1 {
return headers.first(name: "transfer-encoding") == "chunked" ? true : false
} else {
return false
}
}

/// A `ChannelOutboundHandler` that can serialize HTTP requests.
///
/// This channel handler is used to translate messages from a series of
Expand All @@ -136,18 +146,50 @@ public final class HTTPRequestEncoder: ChannelOutboundHandler, RemovableChannelH
public typealias OutboundIn = HTTPClientRequestPart
public typealias OutboundOut = IOData

/// Configuration for the ``HTTPRequestEncoder``.
///
/// This object controls the behaviour of the ``HTTPRequestEncoder``. It enables users to
/// change the default behaviour of the type to better handle a wide range of use-cases.
public struct Configuration: Sendable, Hashable {
/// Whether the ``HTTPRequestEncoder`` should automatically add `Content-Length` or
/// `Transfer-Encoding` headers when appropriate.
///
/// Defaults to `true`.
///
/// Set to `false` if you are confident you are appropriately setting these framing
/// headers yourself, to skip NIO's transformation.
public var automaticallySetFramingHeaders: Bool

public init() {
self.automaticallySetFramingHeaders = true
}
}

private var isChunked = false

public init () { }
private var configuration: Configuration

public convenience init() {
self.init(configuration: Configuration())
}

public init(configuration: Configuration) {
self.configuration = configuration
}


public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
switch self.unwrapOutboundIn(data) {
case .head(var request):
assert(!(request.headers.contains(name: "content-length") &&
request.headers[canonicalForm: "transfer-encoding"].contains("chunked"[...])),
"illegal HTTP sent: \(request) contains both a content-length and transfer-encoding:chunked")
self.isChunked = correctlyFrameTransportHeaders(hasBody: request.method.hasRequestBody,
headers: &request.headers, version: request.version) == .chunked
if self.configuration.automaticallySetFramingHeaders {
self.isChunked = correctlyFrameTransportHeaders(hasBody: request.method.hasRequestBody,
headers: &request.headers, version: request.version) == .chunked
} else {
self.isChunked = messageIsChunked(headers: request.headers, version: request.version)
}

writeHead(wrapOutboundOut: self.wrapOutboundOut, writeStartLine: { buffer in
buffer.write(request: request)
Expand Down Expand Up @@ -177,18 +219,50 @@ public final class HTTPResponseEncoder: ChannelOutboundHandler, RemovableChannel
public typealias OutboundIn = HTTPServerResponsePart
public typealias OutboundOut = IOData

/// Configuration for the ``HTTPResponseEncoder``.
///
/// This object controls the behaviour of the ``HTTPResponseEncoder``. It enables users to
/// change the default behaviour of the type to better handle a wide range of use-cases.
public struct Configuration: Sendable, Hashable {
/// Whether the ``HTTPResponseEncoder`` should automatically add `Content-Length` or
/// `Transfer-Encoding` headers when appropriate.
///
/// Defaults to `true`.
///
/// Set to `false` if you are confident you are appropriately setting these framing
/// headers yourself, to skip NIO's transformation.
public var automaticallySetFramingHeaders: Bool

public init() {
self.automaticallySetFramingHeaders = true
}
}

private var isChunked = false

public init () { }
private var configuration: Configuration

public convenience init() {
self.init(configuration: Configuration())
}

public init(configuration: Configuration) {
self.configuration = configuration
}

public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
switch self.unwrapOutboundIn(data) {
case .head(var response):
assert(!(response.headers.contains(name: "content-length") &&
response.headers[canonicalForm: "transfer-encoding"].contains("chunked"[...])),
"illegal HTTP sent: \(response) contains both a content-length and transfer-encoding:chunked")
self.isChunked = correctlyFrameTransportHeaders(hasBody: response.status.mayHaveResponseBody ? .yes : .no,
headers: &response.headers, version: response.version) == .chunked

if self.configuration.automaticallySetFramingHeaders {
self.isChunked = correctlyFrameTransportHeaders(hasBody: response.status.mayHaveResponseBody ? .yes : .no,
headers: &response.headers, version: response.version) == .chunked
} else {
self.isChunked = messageIsChunked(headers: response.headers, version: response.version)
}

writeHead(wrapOutboundOut: self.wrapOutboundOut, writeStartLine: { buffer in
buffer.write(response: response)
Expand Down
Loading