mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-06-02 07:37:34 +00:00
247 lines
12 KiB
Swift
247 lines
12 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the AsyncHTTPClient open source project
|
|
//
|
|
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
@testable import AsyncHTTPClient
|
|
import Logging
|
|
import NIO
|
|
import NIOHTTP2
|
|
import XCTest
|
|
|
|
class HTTP2IdleHandlerTests: XCTestCase {
|
|
func testReceiveSettingsWithMaxConcurrentStreamSetting() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
}
|
|
|
|
func testReceiveSettingsWithoutMaxConcurrentStreamSetting() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 100, "Expected to assume 100 maxConcurrentConnection, if no setting was present")
|
|
}
|
|
|
|
func testEmptySettingsDontOverwriteMaxConcurrentStreamSetting() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
let emptySettings = HTTP2Frame(streamID: 0, payload: .settings(.settings([])))
|
|
XCTAssertNoThrow(try embedded.writeInbound(emptySettings))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
}
|
|
|
|
func testOverwriteMaxConcurrentStreamSetting() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
let emptySettings = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 20)])))
|
|
XCTAssertNoThrow(try embedded.writeInbound(emptySettings))
|
|
XCTAssertEqual(delegate.maxStreams, 20)
|
|
}
|
|
|
|
func testGoAwayReceivedBeforeSettings() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let randomStreamID = HTTP2StreamID((0..<Int32.max).randomElement()!)
|
|
let goAwayFrame = HTTP2Frame(streamID: randomStreamID, payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil))
|
|
XCTAssertEqual(delegate.goAwayReceived, false)
|
|
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
|
XCTAssertEqual(delegate.goAwayReceived, true)
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
|
|
var inbound: HTTP2Frame?
|
|
XCTAssertNoThrow(inbound = try embedded.readInbound(as: HTTP2Frame.self))
|
|
XCTAssertEqual(randomStreamID, inbound?.streamID)
|
|
}
|
|
|
|
func testGoAwayReceivedAfterSettings() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
let randomStreamID = HTTP2StreamID((0..<Int32.max).randomElement()!)
|
|
let goAwayFrame = HTTP2Frame(streamID: randomStreamID, payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil))
|
|
XCTAssertEqual(delegate.goAwayReceived, false)
|
|
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
|
XCTAssertEqual(delegate.goAwayReceived, true)
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
var settingsInbound: HTTP2Frame?
|
|
XCTAssertNoThrow(settingsInbound = try embedded.readInbound(as: HTTP2Frame.self))
|
|
XCTAssertEqual(0, settingsInbound?.streamID)
|
|
|
|
var goAwayInbound: HTTP2Frame?
|
|
XCTAssertNoThrow(goAwayInbound = try embedded.readInbound(as: HTTP2Frame.self))
|
|
XCTAssertEqual(randomStreamID, goAwayInbound?.streamID)
|
|
}
|
|
|
|
func testCloseEventBeforeFirstSettings() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
XCTAssertTrue(embedded.isActive)
|
|
embedded.pipeline.triggerUserOutboundEvent(HTTPConnectionEvent.closeConnection, promise: nil)
|
|
XCTAssertFalse(embedded.isActive)
|
|
}
|
|
|
|
func testCloseEventWhileNoOpenStreams() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
XCTAssertTrue(embedded.isActive)
|
|
embedded.pipeline.triggerUserOutboundEvent(HTTPConnectionEvent.closeConnection, promise: nil)
|
|
XCTAssertFalse(embedded.isActive)
|
|
}
|
|
|
|
func testCloseEventWhileThereAreOpenStreams() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
var openStreams = Set<HTTP2StreamID>()
|
|
|
|
for i in 0..<(1...100).randomElement()! {
|
|
let streamID = HTTP2StreamID(i)
|
|
let event = NIOHTTP2StreamCreatedEvent(streamID: streamID, localInitialWindowSize: nil, remoteInitialWindowSize: nil)
|
|
embedded.pipeline.fireUserInboundEventTriggered(event)
|
|
openStreams.insert(streamID)
|
|
}
|
|
|
|
embedded.pipeline.triggerUserOutboundEvent(HTTPConnectionEvent.closeConnection, promise: nil)
|
|
XCTAssertTrue(embedded.isActive)
|
|
|
|
while let streamID = openStreams.randomElement() {
|
|
openStreams.remove(streamID)
|
|
|
|
let event = StreamClosedEvent(streamID: streamID, reason: nil)
|
|
XCTAssertTrue(embedded.isActive)
|
|
embedded.pipeline.fireUserInboundEventTriggered(event)
|
|
if openStreams.isEmpty {
|
|
XCTAssertFalse(embedded.isActive)
|
|
} else {
|
|
XCTAssertTrue(embedded.isActive)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testGoAwayWhileThereAreOpenStreams() {
|
|
let delegate = MockHTTP2IdleHandlerDelegate()
|
|
let idleHandler = HTTP2IdleHandler(delegate: delegate, logger: Logger(label: "test"))
|
|
let embedded = EmbeddedChannel(handlers: [idleHandler])
|
|
XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait())
|
|
|
|
let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])))
|
|
XCTAssertEqual(delegate.maxStreams, nil)
|
|
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
var openStreams = Set<HTTP2StreamID>()
|
|
|
|
for i in 0..<(1...100).randomElement()! {
|
|
let streamID = HTTP2StreamID(i)
|
|
let event = NIOHTTP2StreamCreatedEvent(streamID: streamID, localInitialWindowSize: nil, remoteInitialWindowSize: nil)
|
|
embedded.pipeline.fireUserInboundEventTriggered(event)
|
|
openStreams.insert(streamID)
|
|
}
|
|
|
|
let goAwayStreamID = HTTP2StreamID(openStreams.count)
|
|
let goAwayFrame = HTTP2Frame(streamID: goAwayStreamID, payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil))
|
|
XCTAssertEqual(delegate.goAwayReceived, false)
|
|
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
|
XCTAssertEqual(delegate.goAwayReceived, true)
|
|
XCTAssertEqual(delegate.maxStreams, 10)
|
|
|
|
while let streamID = openStreams.randomElement() {
|
|
openStreams.remove(streamID)
|
|
|
|
let event = StreamClosedEvent(streamID: streamID, reason: nil)
|
|
XCTAssertTrue(embedded.isActive)
|
|
embedded.pipeline.fireUserInboundEventTriggered(event)
|
|
if openStreams.isEmpty {
|
|
XCTAssertFalse(embedded.isActive)
|
|
} else {
|
|
XCTAssertTrue(embedded.isActive)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class MockHTTP2IdleHandlerDelegate: HTTP2IdleHandlerDelegate {
|
|
private(set) var maxStreams: Int?
|
|
private(set) var goAwayReceived: Bool = false
|
|
|
|
private(set) var streamClosedHitCount: Int = 0
|
|
|
|
func http2SettingsReceived(maxStreams: Int) {
|
|
self.maxStreams = maxStreams
|
|
}
|
|
|
|
func http2GoAwayReceived() {
|
|
self.goAwayReceived = true
|
|
}
|
|
|
|
func http2StreamClosed(availableStreams: Int) {
|
|
self.streamClosedHitCount += 1
|
|
}
|
|
}
|