2018-03-13 23:21:12 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// This source file is part of the SwiftNIO open source project
|
|
|
|
//
|
2021-08-12 20:49:46 +08:00
|
|
|
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
|
2018-03-13 23:21:12 +08:00
|
|
|
// 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 XCTest
|
2021-08-12 20:49:46 +08:00
|
|
|
import NIOCore
|
|
|
|
import NIOEmbedded
|
2018-08-27 18:09:51 +08:00
|
|
|
@testable import NIOHTTP1
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
private final class ReadRecorder: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = HTTPServerRequestPart
|
|
|
|
|
|
|
|
enum Event: Equatable {
|
|
|
|
case channelRead(InboundIn)
|
|
|
|
case halfClose
|
|
|
|
|
|
|
|
static func ==(lhs: Event, rhs: Event) -> Bool {
|
|
|
|
switch (lhs, rhs) {
|
|
|
|
case (.channelRead(let b1), .channelRead(let b2)):
|
|
|
|
return b1 == b2
|
|
|
|
case (.halfClose, .halfClose):
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public var reads: [Event] = []
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
2018-03-13 23:21:12 +08:00
|
|
|
self.reads.append(.channelRead(self.unwrapInboundIn(data)))
|
2019-02-26 02:20:22 +08:00
|
|
|
context.fireChannelRead(data)
|
2018-03-13 23:21:12 +08:00
|
|
|
}
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
|
2018-03-13 23:21:12 +08:00
|
|
|
switch event {
|
|
|
|
case let evt as ChannelEvent where evt == ChannelEvent.inputClosed:
|
|
|
|
self.reads.append(.halfClose)
|
|
|
|
default:
|
2019-02-26 02:20:22 +08:00
|
|
|
context.fireUserInboundEventTriggered(event)
|
2018-03-13 23:21:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-18 16:48:18 +08:00
|
|
|
private final class WriteRecorder: ChannelOutboundHandler {
|
|
|
|
typealias OutboundIn = HTTPServerResponsePart
|
|
|
|
|
|
|
|
public var writes: [HTTPServerResponsePart] = []
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
|
2018-05-18 16:48:18 +08:00
|
|
|
self.writes.append(self.unwrapOutboundIn(data))
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
context.write(data, promise: promise)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-13 23:21:12 +08:00
|
|
|
private final class ReadCountingHandler: ChannelOutboundHandler {
|
|
|
|
typealias OutboundIn = Any
|
|
|
|
typealias OutboundOut = Any
|
|
|
|
|
|
|
|
public var readCount = 0
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func read(context: ChannelHandlerContext) {
|
2018-03-13 23:21:12 +08:00
|
|
|
self.readCount += 1
|
2019-02-26 02:20:22 +08:00
|
|
|
context.read()
|
2018-03-13 23:21:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
private final class QuiesceEventRecorder: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = Any
|
|
|
|
typealias InboundOut = Any
|
|
|
|
|
|
|
|
public var quiesceCount = 0
|
|
|
|
|
|
|
|
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
|
|
|
|
if event is ChannelShouldQuiesceEvent {
|
|
|
|
quiesceCount += 1
|
|
|
|
}
|
|
|
|
context.fireUserInboundEventTriggered(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-28 21:22:07 +08:00
|
|
|
// This handler drops close mode output. This is because EmbeddedChannel doesn't support it,
|
|
|
|
// and tests here don't require that it does anything sensible.
|
|
|
|
private final class CloseOutputSuppressor: ChannelOutboundHandler {
|
|
|
|
typealias OutboundIn = Any
|
|
|
|
typealias OutboundOut = Any
|
|
|
|
|
|
|
|
func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
|
|
|
|
if mode == .output {
|
|
|
|
promise?.succeed()
|
|
|
|
} else {
|
|
|
|
context.close(mode: mode, promise: promise)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
|
2018-03-13 23:21:12 +08:00
|
|
|
class HTTPServerPipelineHandlerTest: XCTestCase {
|
|
|
|
var channel: EmbeddedChannel! = nil
|
|
|
|
var requestHead: HTTPRequestHead! = nil
|
|
|
|
var responseHead: HTTPResponseHead! = nil
|
|
|
|
fileprivate var readRecorder: ReadRecorder! = nil
|
|
|
|
fileprivate var readCounter: ReadCountingHandler! = nil
|
2018-05-18 16:48:18 +08:00
|
|
|
fileprivate var writeRecorder: WriteRecorder! = nil
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
fileprivate var pipelineHandler: HTTPServerPipelineHandler! = nil
|
|
|
|
fileprivate var quiesceEventRecorder: QuiesceEventRecorder! = nil
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
override func setUp() {
|
|
|
|
self.channel = EmbeddedChannel()
|
|
|
|
self.readRecorder = ReadRecorder()
|
|
|
|
self.readCounter = ReadCountingHandler()
|
2018-05-18 16:48:18 +08:00
|
|
|
self.writeRecorder = WriteRecorder()
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
self.pipelineHandler = HTTPServerPipelineHandler()
|
|
|
|
self.quiesceEventRecorder = QuiesceEventRecorder()
|
2023-04-28 21:22:07 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(CloseOutputSuppressor()).wait())
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(self.readCounter).wait())
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPResponseEncoder()).wait())
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(self.writeRecorder).wait())
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(self.pipelineHandler).wait())
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(self.readRecorder).wait())
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(self.quiesceEventRecorder).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
2021-01-20 01:27:02 +08:00
|
|
|
self.requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/path")
|
2018-03-13 23:21:12 +08:00
|
|
|
self.requestHead.headers.add(name: "Host", value: "example.com")
|
|
|
|
|
2021-01-20 01:27:02 +08:00
|
|
|
self.responseHead = HTTPResponseHead(version: .http1_1, status: .ok)
|
2018-03-13 23:21:12 +08:00
|
|
|
self.responseHead.headers.add(name: "Server", value: "SwiftNIO")
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
// this activates the channel
|
|
|
|
XCTAssertNoThrow(try self.channel.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 1)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
override func tearDown() {
|
2018-05-18 16:48:18 +08:00
|
|
|
if let channel = self.channel {
|
2019-11-28 03:25:06 +08:00
|
|
|
XCTAssertNoThrow(try channel.finish(acceptAlreadyClosed: true))
|
2018-05-18 16:48:18 +08:00
|
|
|
self.channel = nil
|
|
|
|
}
|
2018-03-13 23:21:12 +08:00
|
|
|
self.requestHead = nil
|
|
|
|
self.responseHead = nil
|
|
|
|
self.readRecorder = nil
|
2019-07-31 16:34:06 +08:00
|
|
|
self.readCounter = nil
|
|
|
|
self.writeRecorder = nil
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
self.pipelineHandler = nil
|
|
|
|
self.quiesceEventRecorder = nil
|
2018-03-13 23:21:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testBasicBufferingBehaviour() throws {
|
|
|
|
// Send in 3 requests at once.
|
|
|
|
for _ in 0..<3 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only one request should have made it through.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Unblock by sending a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// Two requests should have made it through now.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now send the last response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// Now all three.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
}
|
|
|
|
|
|
|
|
func testReadCallsAreSuppressedWhenPipelining() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send in a request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Call read again, twice. This should not change the number.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// This should have automatically triggered a call to read(), but only one.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testReadCallsAreSuppressedWhenUnbufferingIfThereIsStillBufferedData() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send in two requests.
|
|
|
|
for _ in 0..<2 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call read again, twice. This should not change the number.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// This should have not triggered a call to read.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Try calling read some more.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Now send in the last response, and see the read go through.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testServerCanRespondEarly() throws {
|
|
|
|
// Send in the first part of a request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
|
|
|
|
// This is still moving forward: we can read.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Now the server sends a response immediately.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// We're still moving forward and can read.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
|
|
|
|
// The client response completes.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// We can still read.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 3)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPipelineHandlerWillBufferHalfClose() throws {
|
|
|
|
// Send in 2 requests at once.
|
|
|
|
for _ in 0..<3 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now half-close the connection.
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
|
|
|
|
// Only one request should have made it through, no half-closure yet.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Unblock by sending a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// Two requests should have made it through now.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now send the last response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// Now the half-closure should be delivered.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.halfClose])
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPipelineHandlerWillDeliverHalfCloseEarly() throws {
|
|
|
|
// Send in a request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Now send a new request but half-close the connection before we get .end.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
|
|
|
|
// Only one request should have made it through, no half-closure yet.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Unblock by sending a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// The second request head, followed by the half-close, should have made it through.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.halfClose])
|
|
|
|
}
|
|
|
|
|
|
|
|
func testAReadIsNotIssuedWhenUnbufferingAHalfCloseAfterRequestComplete() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send in two requests and then half-close.
|
|
|
|
for _ in 0..<2 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
|
|
|
|
// Call read again, twice. This should not change the number.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// This should have not triggered a call to read.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Now send in the last response. This should also not issue a read.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testHalfCloseWhileWaitingForResponseIsPassedAlongIfNothingElseBuffered() throws {
|
|
|
|
// Send in 2 requests at once.
|
|
|
|
for _ in 0..<2 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only one request should have made it through, no half-closure yet.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Unblock by sending a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-03-13 23:21:12 +08:00
|
|
|
|
|
|
|
// Two requests should have made it through now. Still no half-closure.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now send the half-closure.
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
|
|
|
|
// The half-closure should be delivered immediately.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.halfClose])
|
|
|
|
}
|
2018-04-24 15:18:21 +08:00
|
|
|
|
|
|
|
func testRecursiveChannelReadInvocationsDoNotCauseIssues() throws {
|
|
|
|
func makeRequestHead(uri: String) -> HTTPRequestHead {
|
2021-01-20 01:27:02 +08:00
|
|
|
var requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: uri)
|
2018-04-24 15:18:21 +08:00
|
|
|
requestHead.headers.add(name: "Host", value: "example.com")
|
|
|
|
return requestHead
|
|
|
|
}
|
|
|
|
|
|
|
|
class VerifyOrderHandler: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = HTTPServerRequestPart
|
|
|
|
typealias OutboundOut = HTTPServerResponsePart
|
|
|
|
|
|
|
|
enum NextExpectedMessageType {
|
|
|
|
case head
|
|
|
|
case end
|
|
|
|
}
|
|
|
|
enum State {
|
|
|
|
case req1HeadExpected
|
|
|
|
case req1EndExpected
|
|
|
|
case req2HeadExpected
|
|
|
|
case req2EndExpected
|
|
|
|
case req3HeadExpected
|
|
|
|
case req3EndExpected
|
|
|
|
case reqBoomHeadExpected
|
|
|
|
case reqBoomEndExpected
|
|
|
|
|
|
|
|
case done
|
|
|
|
}
|
|
|
|
var state: State = .req1HeadExpected
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
2018-04-24 15:18:21 +08:00
|
|
|
let req = self.unwrapInboundIn(data)
|
|
|
|
switch req {
|
|
|
|
case .head(let head):
|
|
|
|
// except for "req_1", we always send the .end straight away
|
|
|
|
var sendEnd = true
|
|
|
|
switch head.uri {
|
|
|
|
case "/req_1":
|
|
|
|
XCTAssertEqual(.req1HeadExpected, self.state)
|
|
|
|
self.state = .req1EndExpected
|
|
|
|
// for req_1, we don't send the end straight away to force the others to be buffered
|
|
|
|
sendEnd = false
|
|
|
|
case "/req_2":
|
|
|
|
XCTAssertEqual(.req2HeadExpected, self.state)
|
|
|
|
self.state = .req2EndExpected
|
|
|
|
case "/req_3":
|
|
|
|
XCTAssertEqual(.req3HeadExpected, self.state)
|
|
|
|
self.state = .req3EndExpected
|
|
|
|
case "/req_boom":
|
|
|
|
XCTAssertEqual(.reqBoomHeadExpected, self.state)
|
|
|
|
self.state = .reqBoomEndExpected
|
|
|
|
default:
|
|
|
|
XCTFail("didn't expect \(head)")
|
|
|
|
}
|
2021-01-20 01:27:02 +08:00
|
|
|
context.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: .http1_1, status: .ok))), promise: nil)
|
2018-04-24 15:18:21 +08:00
|
|
|
if sendEnd {
|
2019-02-26 02:20:22 +08:00
|
|
|
context.write(self.wrapOutboundOut(.end(nil)), promise: nil)
|
2018-04-24 15:18:21 +08:00
|
|
|
}
|
2019-02-26 02:20:22 +08:00
|
|
|
context.flush()
|
2018-04-24 15:18:21 +08:00
|
|
|
case .end:
|
|
|
|
switch self.state {
|
|
|
|
case .req1EndExpected:
|
|
|
|
self.state = .req2HeadExpected
|
|
|
|
case .req2EndExpected:
|
|
|
|
self.state = .req3HeadExpected
|
|
|
|
|
|
|
|
// this will cause `channelRead` to be recursively called and we need to make sure everything then still works
|
2021-01-20 01:27:02 +08:00
|
|
|
try! (context.channel as! EmbeddedChannel).writeInbound(HTTPServerRequestPart.head(HTTPRequestHead(version: .http1_1, method: .GET, uri: "/req_boom")))
|
2019-02-26 02:20:22 +08:00
|
|
|
try! (context.channel as! EmbeddedChannel).writeInbound(HTTPServerRequestPart.end(nil))
|
2018-04-24 15:18:21 +08:00
|
|
|
case .req3EndExpected:
|
|
|
|
self.state = .reqBoomHeadExpected
|
|
|
|
case .reqBoomEndExpected:
|
|
|
|
self.state = .done
|
|
|
|
default:
|
|
|
|
XCTFail("illegal state for end: \(self.state)")
|
|
|
|
}
|
|
|
|
case .body:
|
|
|
|
XCTFail("we don't send any bodies")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let handler = VerifyOrderHandler()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
2018-04-24 15:18:21 +08:00
|
|
|
|
|
|
|
for f in 1...3 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(makeRequestHead(uri: "/req_\(f)"))))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we should have delivered the first request, with the second and third buffered because req_1's .end
|
|
|
|
// doesn't get sent by the handler (instead we'll do that below)
|
|
|
|
XCTAssertEqual(.req2HeadExpected, handler.state)
|
|
|
|
|
|
|
|
// finish 1st request, that will send through the 2nd one which will then write the 'req_boom' request
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
|
|
|
|
|
|
|
XCTAssertEqual(.done, handler.state)
|
|
|
|
}
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
func testQuiescingEventWhenInitiallyIdle() throws {
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingEventWhenIdleAfterARequest() throws {
|
|
|
|
// Send through one request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// The request should have made it through.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
// No further events should have happened.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingInTheMiddleOfARequestNoResponseBitsYet() throws {
|
|
|
|
// Send through only the head.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead))])
|
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Now send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
// still missing the request .end
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
var reqWithConnectionClose: HTTPResponseHead = self.responseHead
|
|
|
|
reqWithConnectionClose.headers.add(name: "connection", value: "close")
|
|
|
|
XCTAssertEqual([HTTPServerResponsePart.head(reqWithConnectionClose),
|
|
|
|
HTTPServerResponsePart.end(nil)],
|
|
|
|
self.writeRecorder.writes)
|
|
|
|
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingAfterHavingReceivedRequestButBeforeResponseWasSent() throws {
|
|
|
|
// Send through a full request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Now send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
|
|
|
|
|
|
|
var reqWithConnectionClose: HTTPResponseHead = self.responseHead
|
|
|
|
reqWithConnectionClose.headers.add(name: "connection", value: "close")
|
|
|
|
XCTAssertEqual([HTTPServerResponsePart.head(reqWithConnectionClose),
|
|
|
|
HTTPServerResponsePart.end(nil)],
|
|
|
|
self.writeRecorder.writes)
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingAfterHavingReceivedRequestAndResponseHeadButNoResponseEndYet() throws {
|
|
|
|
// Send through a full request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now send the response .head.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Now send the response .end.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
XCTAssertFalse(self.channel.isActive)
|
|
|
|
|
|
|
|
XCTAssertEqual([HTTPServerResponsePart.head(self.responseHead),
|
|
|
|
HTTPServerResponsePart.end(nil)],
|
|
|
|
self.writeRecorder.writes)
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingAfterRequestAndResponseHeadsButBeforeAnyEndsThenRequestEndBeforeResponseEnd() throws {
|
|
|
|
// Send through a request .head.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead))])
|
|
|
|
|
|
|
|
// Now send the response .head.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Request .end.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Response .end.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
XCTAssertFalse(self.channel.isActive)
|
|
|
|
|
|
|
|
XCTAssertEqual([HTTPServerResponsePart.head(self.responseHead),
|
|
|
|
HTTPServerResponsePart.end(nil)],
|
|
|
|
self.writeRecorder.writes)
|
|
|
|
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingAfterRequestAndResponseHeadsButBeforeAnyEndsThenRequestEndAfterResponseEnd() throws {
|
|
|
|
// Send through a request .head.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead))])
|
|
|
|
|
|
|
|
// Now send the response .head.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Response .end.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Request .end.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
|
|
|
|
|
|
|
XCTAssertEqual([HTTPServerResponsePart.head(self.responseHead),
|
|
|
|
HTTPServerResponsePart.end(nil)],
|
|
|
|
self.writeRecorder.writes)
|
|
|
|
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingAfterHavingReceivedOneRequestButBeforeResponseWasSentWithMoreRequestsInTheBuffer() throws {
|
|
|
|
// Send through a full request and buffer a few more
|
|
|
|
for _ in 0..<3 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that only one request came through
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
|
|
|
|
// Now send a response.
|
2018-05-21 22:29:19 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
2018-05-18 16:48:18 +08:00
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
|
|
|
|
|
|
|
var reqWithConnectionClose: HTTPResponseHead = self.responseHead
|
|
|
|
reqWithConnectionClose.headers.add(name: "connection", value: "close")
|
|
|
|
|
|
|
|
// check that only one response (with connection: close) came through
|
|
|
|
XCTAssertEqual([HTTPServerResponsePart.head(reqWithConnectionClose),
|
|
|
|
HTTPServerResponsePart.end(nil)],
|
|
|
|
self.writeRecorder.writes)
|
|
|
|
|
|
|
|
// Check that only one request came through
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
XCTAssertFalse(self.channel.isActive)
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
2018-05-18 16:48:18 +08:00
|
|
|
}
|
2018-08-27 18:09:51 +08:00
|
|
|
|
|
|
|
func testParserErrorOnly() throws {
|
|
|
|
class VerifyOrderHandler: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = HTTPServerRequestPart
|
|
|
|
typealias OutboundOut = HTTPServerResponsePart
|
|
|
|
|
|
|
|
enum State {
|
|
|
|
case errorExpected
|
|
|
|
case done
|
|
|
|
}
|
|
|
|
var state: State = .errorExpected
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func errorCaught(context: ChannelHandlerContext, error: Error) {
|
2022-09-13 21:09:57 +08:00
|
|
|
XCTAssertEqual(HTTPParserError.unknown, error as? HTTPParserError)
|
2018-08-27 18:09:51 +08:00
|
|
|
XCTAssertEqual(.errorExpected, self.state)
|
|
|
|
self.state = .done
|
|
|
|
}
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
2018-08-27 18:09:51 +08:00
|
|
|
XCTFail("no requests expected")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let handler = VerifyOrderHandler()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try self.channel.pipeline.addHandler(HTTPServerProtocolErrorHandler()).wait())
|
|
|
|
XCTAssertNoThrow(try self.channel.pipeline.addHandler(handler).wait())
|
2018-08-27 18:09:51 +08:00
|
|
|
|
2022-09-13 21:09:57 +08:00
|
|
|
self.channel.pipeline.fireErrorCaught(HTTPParserError.unknown)
|
2018-08-27 18:09:51 +08:00
|
|
|
|
|
|
|
XCTAssertEqual(.done, handler.state)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testLegitRequestFollowedByParserErrorArrivingWhilstResponseOutstanding() throws {
|
|
|
|
func makeRequestHead(uri: String) -> HTTPRequestHead {
|
2021-01-20 01:27:02 +08:00
|
|
|
var requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: uri)
|
2018-08-27 18:09:51 +08:00
|
|
|
requestHead.headers.add(name: "Host", value: "example.com")
|
|
|
|
return requestHead
|
|
|
|
}
|
|
|
|
|
|
|
|
class VerifyOrderHandler: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = HTTPServerRequestPart
|
|
|
|
typealias OutboundOut = HTTPServerResponsePart
|
|
|
|
|
|
|
|
enum State {
|
|
|
|
case reqHeadExpected
|
|
|
|
case reqEndExpected
|
|
|
|
case errorExpected
|
|
|
|
case done
|
|
|
|
}
|
|
|
|
var state: State = .reqHeadExpected
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func errorCaught(context: ChannelHandlerContext, error: Error) {
|
2018-08-27 18:09:51 +08:00
|
|
|
XCTAssertEqual(HTTPParserError.closedConnection, error as? HTTPParserError)
|
|
|
|
XCTAssertEqual(.errorExpected, self.state)
|
|
|
|
self.state = .done
|
|
|
|
}
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
2018-08-27 18:09:51 +08:00
|
|
|
switch self.unwrapInboundIn(data) {
|
|
|
|
case .head:
|
|
|
|
// We dispatch this to the event loop so that it doesn't happen immediately but rather can be
|
|
|
|
// run from the driving test code whenever it wants by running the EmbeddedEventLoop.
|
2019-02-26 02:20:22 +08:00
|
|
|
context.eventLoop.execute {
|
2021-01-20 01:27:02 +08:00
|
|
|
context.writeAndFlush(self.wrapOutboundOut(.head(.init(version: .http1_1,
|
2018-08-27 18:09:51 +08:00
|
|
|
status: .ok))),
|
|
|
|
promise: nil)
|
|
|
|
}
|
|
|
|
XCTAssertEqual(.reqHeadExpected, self.state)
|
|
|
|
self.state = .reqEndExpected
|
|
|
|
case .body:
|
|
|
|
XCTFail("no body expected")
|
|
|
|
case .end:
|
|
|
|
// We dispatch this to the event loop so that it doesn't happen immediately but rather can be
|
|
|
|
// run from the driving test code whenever it wants by running the EmbeddedEventLoop.
|
2019-02-26 02:20:22 +08:00
|
|
|
context.eventLoop.execute {
|
|
|
|
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
|
2018-08-27 18:09:51 +08:00
|
|
|
}
|
|
|
|
XCTAssertEqual(.reqEndExpected, self.state)
|
|
|
|
self.state = .errorExpected
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let handler = VerifyOrderHandler()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try self.channel.pipeline.addHandler(HTTPServerProtocolErrorHandler()).wait())
|
|
|
|
XCTAssertNoThrow(try self.channel.pipeline.addHandler(handler).wait())
|
2018-08-27 18:09:51 +08:00
|
|
|
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(makeRequestHead(uri: "/one"))))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
self.channel.pipeline.fireErrorCaught(HTTPParserError.closedConnection)
|
|
|
|
|
|
|
|
// let's now run the HTTP responses that we enqueued earlier on.
|
|
|
|
(self.channel.eventLoop as! EmbeddedEventLoop).run()
|
|
|
|
XCTAssertEqual(.done, handler.state)
|
|
|
|
}
|
Make pipeline handler behave better when removed (#1080)
Motivation:
The HTTPServerPipelineHandler is removed from pipelines in many cases,
but the most common case is over upgrade. While the handler is
removable, it doesn't make any effort to ensure that it leaves the
pipeline in a sensible state, which is pretty awkward.
In particular, there are 3 things the pipeline handler may be holding
on to that can lead to damage. The first is pipelined requests: if there
are any, they should be delivered, as the user may be deliberately
allowing pipelining.
The second thing is read() calls. The HTTPServerPipelineHandler exerts
backpressure on clients that aggressively pipeline by refusing to read
from the socket. If that happens, and then the handler is removed from
the channel, it will "forget" to restart reading from the socket on the
way out. That leaves the channel quietly in a state where no reads will
occur ever again, which is pretty uncool.
The third thing is quiescing. The HTTPServerPipelineHandler catches
quiescing events and allows them to deliver a response before closing a
connection. If that has happened when the pipeline handler is removed,
it should fall back to the behaviour as though it were not there.
Modifications:
- Added a handlerRemoved implementation to play event state that should
be replayed.
- Added a channelInactive implementation to drop data.
Result:
More graceful handler removal.
2019-07-24 03:10:26 +08:00
|
|
|
|
|
|
|
func testRemovingWithResponseOutstandingTriggersRead() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send in a request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Call read again, twice. This should not change the number.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Remove the handler.
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.removeHandler(self.pipelineHandler).wait())
|
|
|
|
|
|
|
|
// This should have automatically triggered a call to read(), but only one.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
|
|
|
|
// Incidentally we shouldn't have fired a quiesce event.
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRemovingWithPartialResponseOutstandingTriggersRead() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send in a request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Call read again, twice. This should not change the number.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send a partial response, which should not trigger a read.
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Remove the handler.
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.removeHandler(self.pipelineHandler).wait())
|
|
|
|
|
|
|
|
// This should have automatically triggered a call to read(), but only one.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
|
|
|
|
// Incidentally we shouldn't have fired a quiesce event.
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRemovingWithBufferedRequestForwards() throws {
|
|
|
|
// Send in a request, and part of another.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
|
|
|
|
// Only one request should have made it through.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Remove the handler.
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.removeHandler(self.pipelineHandler).wait())
|
|
|
|
|
|
|
|
// The extra data should have been forwarded.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil)),
|
|
|
|
.channelRead(HTTPServerRequestPart.head(self.requestHead))])
|
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingInAResponseThenRemovedFiresEventAndReads() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send through a request and part of another.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
|
|
|
|
// Only one request should have made it through.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Now quiesce the channel.
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Call read again, twice. This should not lead to reads, as we're waiting for the end.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Now remove the handler.
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.removeHandler(self.pipelineHandler).wait())
|
|
|
|
|
|
|
|
// Channel should be open, but the quiesce event should have fired, and read
|
|
|
|
// shouldn't have been called as we aren't expecting more data.
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 1)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testQuiescingInAResponseThenRemovedFiresEventAndDoesntRead() throws {
|
|
|
|
// First, call read() and check it makes it through.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Send through just the head.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead))])
|
|
|
|
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// Call read again, twice. This should pass through.
|
|
|
|
self.channel.read()
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 0)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 3)
|
|
|
|
|
|
|
|
// Now remove the handler.
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.removeHandler(self.pipelineHandler).wait())
|
|
|
|
|
|
|
|
// Channel should be open, but the quiesce event should have fired, and read
|
|
|
|
// shouldn't be (as it passed through.
|
|
|
|
XCTAssertTrue(self.channel.isActive)
|
|
|
|
XCTAssertEqual(self.quiesceEventRecorder.quiesceCount, 1)
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 3)
|
|
|
|
}
|
2021-11-09 18:20:51 +08:00
|
|
|
|
|
|
|
func testServerCanRespondContinue() throws {
|
|
|
|
// Send in the first part of a request.
|
|
|
|
var expect100ContinueHead = self.requestHead!
|
|
|
|
expect100ContinueHead.headers.replaceOrAdd(name: "expect", value: "100-continue")
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(expect100ContinueHead)))
|
|
|
|
|
|
|
|
var continueResponse = self.responseHead
|
|
|
|
continueResponse!.status = .continue
|
|
|
|
|
|
|
|
// Now the server sends a continue response.
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(continueResponse!)).wait())
|
|
|
|
|
|
|
|
// The client response completes.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Now the server sends the final response.
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
|
|
|
}
|
|
|
|
|
|
|
|
func testServerCanRespondProcessingMultipleTimes() throws {
|
|
|
|
// Send in a request.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// We haven't completed our response, so no more reading
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
var processResponse: HTTPResponseHead = self.responseHead!
|
|
|
|
processResponse.status = .processing
|
|
|
|
|
|
|
|
// Now the server sends multiple processing responses.
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(processResponse)).wait())
|
|
|
|
|
|
|
|
// We are processing... Reading not allowed
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
// Continue processing...
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(processResponse)).wait())
|
|
|
|
|
|
|
|
// We are processing... Reading not allowed
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
// Continue processing...
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(processResponse)).wait())
|
|
|
|
|
|
|
|
// We are processing... Reading not allowed
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
// Now send the actual response!
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.head(self.responseHead)).wait())
|
|
|
|
XCTAssertNoThrow(try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait())
|
|
|
|
|
|
|
|
// This should have triggered a read
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
}
|
2023-04-28 21:22:07 +08:00
|
|
|
|
|
|
|
func testServerCloseOutputForcesReadsBackOn() throws {
|
|
|
|
// Send in a request
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Reads are blocked.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now the server sends close output
|
|
|
|
XCTAssertNoThrow(try channel.close(mode: .output).wait())
|
|
|
|
|
|
|
|
// This unblocked the read and further reads can continue.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testCloseOutputAlwaysAllowsReads() throws {
|
|
|
|
// Send in a request
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Reads are blocked.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Now the server sends close output
|
|
|
|
XCTAssertNoThrow(try channel.close(mode: .output).wait())
|
|
|
|
|
|
|
|
// This unblocked the read and further reads can continue.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
|
|
|
|
// New requests can come in, but are dropped.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Reads keep working.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 3)
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
}
|
|
|
|
|
|
|
|
func testCloseOutputFirstIsOkEvenIfItsABitWeird() throws {
|
|
|
|
// Server sends close output first
|
|
|
|
XCTAssertNoThrow(try channel.close(mode: .output).wait())
|
|
|
|
|
|
|
|
// Send in a request
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Reads are unblocked.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
|
|
|
|
// But the data is dropped.
|
|
|
|
XCTAssertEqual(self.readRecorder.reads, [])
|
|
|
|
|
|
|
|
// New requests can come in, and are dropped.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Reads keep working.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 1)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 2)
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads, [])
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPipelinedRequestsAreDroppedWhenWeSendCloseOutput() throws {
|
|
|
|
// Send in three requests
|
|
|
|
for _ in 0..<3 {
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reads are blocked and only one request was read.
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
self.channel.read()
|
|
|
|
XCTAssertEqual(self.readCounter.readCount, 0)
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
|
|
|
|
// Server sends close mode output. The buffered requests are dropped.
|
|
|
|
XCTAssertNoThrow(try channel.close(mode: .output).wait())
|
|
|
|
|
|
|
|
XCTAssertEqual(self.readRecorder.reads,
|
|
|
|
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
|
|
|
|
.channelRead(HTTPServerRequestPart.end(nil))])
|
|
|
|
}
|
2023-05-09 17:52:48 +08:00
|
|
|
|
|
|
|
func testWritesAfterCloseOutputAreDropped() throws {
|
|
|
|
// Send in a request
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))
|
|
|
|
|
|
|
|
// Server sends a head.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeOutbound(HTTPServerResponsePart.head(self.responseHead)))
|
|
|
|
|
|
|
|
// Now the server sends close output
|
|
|
|
XCTAssertNoThrow(try channel.close(mode: .output).wait())
|
|
|
|
|
|
|
|
// Now, in error, the server sends .body and .end. Both pass unannounced.
|
|
|
|
XCTAssertNoThrow(try self.channel.writeOutbound(HTTPServerResponsePart.body(.byteBuffer(ByteBuffer()))))
|
|
|
|
XCTAssertNoThrow(try self.channel.writeOutbound(HTTPServerResponsePart.end(nil)))
|
|
|
|
}
|
2018-03-13 23:21:12 +08:00
|
|
|
}
|