mirror of
https://github.com/apple/swift-nio.git
synced 2026-05-20 20:30:36 +00:00
c9756e1083
* Apply formatting
* Apply no block comments rule
* Apply OmitExplicitReturns
* Apple OnlyOneTrailingClosureArgument
* Apply NoAssignmentInExpressions
* Fix up DontRepeatTypeInStaticProperties lint errors
* Apply `OrderedImports`
* Apply `ReplaceForEachWithForLoop`
* format file
* Enable the formatting pipeline
* Adopt `AmbiguousTrailingClosureOverload`
* Fix license header
* Fix format check
* Fix `EndOfLineComment`
* Fix CI
* Adapt CI script to check if changes when running formatting
* Separate lint and format into to steps
* Fix format
* Adopt `UseEarlyExits`
* Revert "Adopt `UseEarlyExits`"
This reverts commit d1ac5bbe12.
462 lines
19 KiB
Swift
462 lines
19 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 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 NIOEmbedded
|
|
import XCTest
|
|
|
|
@testable import NIOCore
|
|
@testable import NIOHTTP1
|
|
|
|
extension ByteBuffer {
|
|
fileprivate func assertContainsOnly(_ string: String) {
|
|
let innerData = self.getString(at: self.readerIndex, length: self.readableBytes)!
|
|
XCTAssertEqual(innerData, string)
|
|
}
|
|
}
|
|
|
|
extension HTTPRequestEncoder.Configuration {
|
|
fileprivate static let noFramingTransformation: HTTPRequestEncoder.Configuration = {
|
|
var config = HTTPRequestEncoder.Configuration()
|
|
config.automaticallySetFramingHeaders = false
|
|
return config
|
|
}()
|
|
}
|
|
|
|
class HTTPRequestEncoderTests: XCTestCase {
|
|
private func sendRequest(
|
|
withMethod method: HTTPMethod,
|
|
andHeaders headers: HTTPHeaders,
|
|
configuration: HTTPRequestEncoder.Configuration = .init()
|
|
) throws -> ByteBuffer {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
try channel.pipeline.syncOperations.addHandler(HTTPRequestEncoder(configuration: configuration))
|
|
var request = HTTPRequestHead(version: .http1_1, method: method, uri: "/uri")
|
|
request.headers = headers
|
|
try channel.writeOutbound(HTTPClientRequestPart.head(request))
|
|
if let buffer = try channel.readOutbound(as: ByteBuffer.self) {
|
|
return buffer
|
|
} else {
|
|
fatalError("Could not read ByteBuffer from channel")
|
|
}
|
|
}
|
|
|
|
func testNoAutoHeadersForHEAD() throws {
|
|
let writtenData = try sendRequest(withMethod: .HEAD, andHeaders: HTTPHeaders())
|
|
writtenData.assertContainsOnly("HEAD /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testNoAutoHeadersForGET() throws {
|
|
let writtenData = try sendRequest(withMethod: .GET, andHeaders: HTTPHeaders())
|
|
writtenData.assertContainsOnly("GET /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testNoAutoHeadersForPOSTWhenDisabled() throws {
|
|
let writtenData = try sendRequest(
|
|
withMethod: .POST,
|
|
andHeaders: HTTPHeaders(),
|
|
configuration: .noFramingTransformation
|
|
)
|
|
writtenData.assertContainsOnly("POST /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testGETContentHeadersLeftAlone() throws {
|
|
var headers = HTTPHeaders([("content-length", "17")])
|
|
var writtenData = try sendRequest(withMethod: .GET, andHeaders: headers)
|
|
writtenData.assertContainsOnly("GET /uri HTTP/1.1\r\ncontent-length: 17\r\n\r\n")
|
|
|
|
headers = HTTPHeaders([("transfer-encoding", "chunked")])
|
|
writtenData = try sendRequest(withMethod: .GET, andHeaders: headers)
|
|
writtenData.assertContainsOnly("GET /uri HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
}
|
|
|
|
func testContentLengthHeadersForHEAD() throws {
|
|
let headers = HTTPHeaders([("content-length", "0")])
|
|
let writtenData = try sendRequest(withMethod: .HEAD, andHeaders: headers)
|
|
writtenData.assertContainsOnly("HEAD /uri HTTP/1.1\r\ncontent-length: 0\r\n\r\n")
|
|
}
|
|
|
|
func testTransferEncodingHeadersForHEAD() throws {
|
|
let headers = HTTPHeaders([("transfer-encoding", "chunked")])
|
|
let writtenData = try sendRequest(withMethod: .HEAD, andHeaders: headers)
|
|
writtenData.assertContainsOnly("HEAD /uri HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
}
|
|
|
|
func testNoContentLengthHeadersForTRACE() throws {
|
|
let headers = HTTPHeaders([("content-length", "0")])
|
|
let writtenData = try sendRequest(withMethod: .TRACE, andHeaders: headers)
|
|
writtenData.assertContainsOnly("TRACE /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testAllowContentLengthHeadersWhenForced_forTRACE() throws {
|
|
let headers = HTTPHeaders([("content-length", "0")])
|
|
let writtenData = try sendRequest(
|
|
withMethod: .TRACE,
|
|
andHeaders: headers,
|
|
configuration: .noFramingTransformation
|
|
)
|
|
writtenData.assertContainsOnly("TRACE /uri HTTP/1.1\r\ncontent-length: 0\r\n\r\n")
|
|
}
|
|
|
|
func testNoTransferEncodingHeadersForTRACE() throws {
|
|
let headers = HTTPHeaders([("transfer-encoding", "chunked")])
|
|
let writtenData = try sendRequest(withMethod: .TRACE, andHeaders: headers)
|
|
writtenData.assertContainsOnly("TRACE /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testAllowTransferEncodingHeadersWhenForced_forTRACE() throws {
|
|
let headers = HTTPHeaders([("transfer-encoding", "chunked")])
|
|
let writtenData = try sendRequest(
|
|
withMethod: .TRACE,
|
|
andHeaders: headers,
|
|
configuration: .noFramingTransformation
|
|
)
|
|
writtenData.assertContainsOnly("TRACE /uri HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
}
|
|
|
|
func testNoChunkedEncodingForHTTP10() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.syncOperations.addHandler(HTTPRequestEncoder()))
|
|
|
|
// This request contains neither Transfer-Encoding: chunked or Content-Length.
|
|
let request = HTTPRequestHead(version: .http1_0, method: .GET, uri: "/uri")
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.head(request)))
|
|
let writtenData = try channel.readOutbound(as: ByteBuffer.self)!
|
|
let writtenResponse = writtenData.getString(at: writtenData.readerIndex, length: writtenData.readableBytes)!
|
|
XCTAssertEqual(writtenResponse, "GET /uri HTTP/1.0\r\n\r\n")
|
|
}
|
|
|
|
func testBody() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
try channel.pipeline.syncOperations.addHandler(HTTPRequestEncoder())
|
|
var request = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/uri")
|
|
request.headers.add(name: "content-length", value: "4")
|
|
|
|
var buf = channel.allocator.buffer(capacity: 4)
|
|
buf.writeStaticString("test")
|
|
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.head(request)))
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buf))))
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(nil)))
|
|
|
|
assertOutboundContainsOnly(channel, "POST /uri HTTP/1.1\r\ncontent-length: 4\r\n\r\n")
|
|
assertOutboundContainsOnly(channel, "test")
|
|
assertOutboundContainsOnly(channel, "")
|
|
}
|
|
|
|
func testCONNECT() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
let uri = "server.example.com:80"
|
|
try channel.pipeline.syncOperations.addHandler(HTTPRequestEncoder())
|
|
var request = HTTPRequestHead(version: .http1_1, method: .CONNECT, uri: uri)
|
|
request.headers.add(name: "Host", value: uri)
|
|
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.head(request)))
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(nil)))
|
|
|
|
assertOutboundContainsOnly(channel, "CONNECT \(uri) HTTP/1.1\r\nHost: \(uri)\r\n\r\n")
|
|
assertOutboundContainsOnly(channel, "")
|
|
}
|
|
|
|
func testChunkedEncodingIsTheDefault() {
|
|
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
|
var buffer = channel.allocator.buffer(capacity: 16)
|
|
var expected = channel.allocator.buffer(capacity: 32)
|
|
|
|
XCTAssertNoThrow(
|
|
try channel.writeOutbound(
|
|
HTTPClientRequestPart.head(
|
|
.init(
|
|
version: .http1_1,
|
|
method: .POST,
|
|
uri: "/"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
buffer.writeString("foo")
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buffer))))
|
|
|
|
expected.clear()
|
|
expected.writeString("3\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
expected.clear()
|
|
expected.writeString("foo")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
expected.clear()
|
|
expected.writeString("\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
expected.clear()
|
|
expected.writeString("0\r\n\r\n")
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(nil)))
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
|
}
|
|
|
|
func testChunkedEncodingCanBetEnabled() {
|
|
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
|
var buffer = channel.allocator.buffer(capacity: 16)
|
|
var expected = channel.allocator.buffer(capacity: 32)
|
|
|
|
XCTAssertNoThrow(
|
|
try channel.writeOutbound(
|
|
HTTPClientRequestPart.head(
|
|
.init(
|
|
version: .http1_1,
|
|
method: .POST,
|
|
uri: "/",
|
|
headers: ["TrAnSfEr-encoding": "chuNKED"]
|
|
)
|
|
)
|
|
)
|
|
)
|
|
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
buffer.writeString("foo")
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buffer))))
|
|
|
|
expected.clear()
|
|
expected.writeString("3\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
expected.clear()
|
|
expected.writeString("foo")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
expected.clear()
|
|
expected.writeString("\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
expected.clear()
|
|
expected.writeString("0\r\n\r\n")
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(nil)))
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
|
}
|
|
|
|
func testChunkedEncodingDealsWithZeroLengthChunks() {
|
|
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
|
var buffer = channel.allocator.buffer(capacity: 16)
|
|
var expected = channel.allocator.buffer(capacity: 32)
|
|
|
|
XCTAssertNoThrow(
|
|
try channel.writeOutbound(
|
|
HTTPClientRequestPart.head(
|
|
.init(
|
|
version: .http1_1,
|
|
method: .POST,
|
|
uri: "/"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
buffer.clear()
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buffer))))
|
|
XCTAssertNoThrow(XCTAssertEqual(0, try channel.readOutbound(as: ByteBuffer.self)?.readableBytes))
|
|
|
|
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(["foo": "bar"])))
|
|
|
|
expected.clear()
|
|
expected.writeString("0\r\nfoo: bar\r\n\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
|
}
|
|
|
|
func testChunkedEncodingWorksIfNoPromisesAreAttachedToTheWrites() {
|
|
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
|
var buffer = channel.allocator.buffer(capacity: 16)
|
|
var expected = channel.allocator.buffer(capacity: 32)
|
|
|
|
channel.write(
|
|
HTTPClientRequestPart.head(
|
|
.init(
|
|
version: .http1_1,
|
|
method: .POST,
|
|
uri: "/"
|
|
)
|
|
),
|
|
promise: nil
|
|
)
|
|
channel.flush()
|
|
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
buffer.writeString("foo")
|
|
channel.write(HTTPClientRequestPart.body(.byteBuffer(buffer)), promise: nil)
|
|
channel.flush()
|
|
|
|
expected.clear()
|
|
expected.writeString("3\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
expected.clear()
|
|
expected.writeString("foo")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
expected.clear()
|
|
expected.writeString("\r\n")
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
expected.clear()
|
|
expected.writeString("0\r\n\r\n")
|
|
channel.write(HTTPClientRequestPart.end(nil), promise: nil)
|
|
channel.flush()
|
|
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
|
}
|
|
|
|
func testFullPipelineCanDisableFramingHeaders_withFutures() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
try channel.pipeline.addHTTPClientHandlers(encoderConfiguration: .noFramingTransformation).wait()
|
|
let request = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/uri")
|
|
try channel.writeOutbound(HTTPClientRequestPart.head(request))
|
|
guard let buffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
buffer.assertContainsOnly("POST /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testFullPipelineCanDisableFramingHeaders_syncOperations() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
try channel.pipeline.syncOperations.addHTTPClientHandlers(encoderConfiguration: .noFramingTransformation)
|
|
let request = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/uri")
|
|
try channel.writeOutbound(HTTPClientRequestPart.head(request))
|
|
guard let buffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
buffer.assertContainsOnly("POST /uri HTTP/1.1\r\n\r\n")
|
|
}
|
|
|
|
func testFullPipelineCanDisableFramingHeaders_sendWithoutChunked() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
try channel.pipeline.addHTTPClientHandlers(encoderConfiguration: .noFramingTransformation).wait()
|
|
let request = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/uri")
|
|
try channel.writeOutbound(HTTPClientRequestPart.head(request))
|
|
guard let headBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
headBuffer.assertContainsOnly("POST /uri HTTP/1.1\r\n\r\n")
|
|
|
|
let body = ByteBuffer(string: "hello world!")
|
|
try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(body)))
|
|
guard let bodyBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
XCTAssertEqual(bodyBuffer, body)
|
|
|
|
try channel.writeOutbound(HTTPClientRequestPart.end(nil))
|
|
guard let trailerBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
XCTAssertEqual(trailerBuffer.readableBytes, 0)
|
|
}
|
|
|
|
func testFullPipelineCanDisableFramingHeaders_sendWithChunked() throws {
|
|
let channel = EmbeddedChannel()
|
|
defer {
|
|
XCTAssertEqual(true, try? channel.finish().isClean)
|
|
}
|
|
|
|
try channel.pipeline.addHTTPClientHandlers(encoderConfiguration: .noFramingTransformation).wait()
|
|
let request = HTTPRequestHead(
|
|
version: .http1_1,
|
|
method: .POST,
|
|
uri: "/uri",
|
|
headers: ["transfer-encoding": "chunked"]
|
|
)
|
|
try channel.writeOutbound(HTTPClientRequestPart.head(request))
|
|
guard let headBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
headBuffer.assertContainsOnly("POST /uri HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
|
|
|
let body = ByteBuffer(string: "hello world!")
|
|
try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(body)))
|
|
guard let prefixBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
guard let bodyBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
guard let suffixBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
XCTAssertEqual(prefixBuffer, ByteBuffer(string: "c\r\n"))
|
|
XCTAssertEqual(bodyBuffer, body)
|
|
XCTAssertEqual(suffixBuffer, ByteBuffer(string: "\r\n"))
|
|
|
|
try channel.writeOutbound(HTTPClientRequestPart.end(nil))
|
|
guard let trailerBuffer = try channel.readOutbound(as: ByteBuffer.self) else {
|
|
XCTFail("Unable to read buffer")
|
|
return
|
|
}
|
|
XCTAssertEqual(trailerBuffer, ByteBuffer(string: "0\r\n\r\n"))
|
|
}
|
|
|
|
private func assertOutboundContainsOnly(_ channel: EmbeddedChannel, _ expected: String) {
|
|
XCTAssertNoThrow(
|
|
XCTAssertNotNil(
|
|
try channel.readOutbound(as: ByteBuffer.self).map { buffer in
|
|
buffer.assertContainsOnly(expected)
|
|
},
|
|
"couldn't read ByteBuffer"
|
|
)
|
|
)
|
|
}
|
|
}
|