| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- /*
- * Copyright 2020, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import NIOCore
- import NIOEmbedded
- import NIOHTTP2
- import NIOTLS
- import XCTest
- @testable import GRPC
- class GRPCServerPipelineConfiguratorTests: GRPCTestCase {
- private var channel: EmbeddedChannel!
- private func assertConfigurator(isPresent: Bool) {
- assertThat(
- try self.channel.pipeline.handler(type: GRPCServerPipelineConfigurator.self).wait(),
- isPresent ? .doesNotThrow() : .throws()
- )
- }
- private func assertHTTP2Handler(isPresent: Bool) {
- assertThat(
- try self.channel.pipeline.handler(type: NIOHTTP2Handler.self).wait(),
- isPresent ? .doesNotThrow() : .throws()
- )
- }
- private func assertGRPCWebToHTTP2Handler(isPresent: Bool) {
- assertThat(
- try self.channel.pipeline.handler(type: GRPCWebToHTTP2ServerCodec.self).wait(),
- isPresent ? .doesNotThrow() : .throws()
- )
- }
- private func setUp(tls: Bool, requireALPN: Bool = true) {
- self.channel = EmbeddedChannel()
- var configuration = Server.Configuration.default(
- target: .unixDomainSocket("/ignored"),
- eventLoopGroup: self.channel.eventLoop,
- serviceProviders: []
- )
- configuration.logger = self.serverLogger
- if tls {
- #if canImport(NIOSSL)
- configuration.tlsConfiguration = .makeServerConfigurationBackedByNIOSSL(
- certificateChain: [],
- privateKey: .file("not used"),
- requireALPN: requireALPN
- )
- #else
- fatalError("TLS enabled for a test when NIOSSL is not available")
- #endif
- }
- let handler = GRPCServerPipelineConfigurator(configuration: configuration)
- assertThat(try self.channel.pipeline.addHandler(handler).wait(), .doesNotThrow())
- }
- #if canImport(NIOSSL)
- func testHTTP2SetupViaALPN() {
- self.setUp(tls: true, requireALPN: true)
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: "h2")
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- func testGRPCExpSetupViaALPN() {
- self.setUp(tls: true, requireALPN: true)
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: "grpc-exp")
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- func testHTTP1Dot1SetupViaALPN() {
- self.setUp(tls: true, requireALPN: true)
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: "http/1.1")
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.assertConfigurator(isPresent: false)
- self.assertGRPCWebToHTTP2Handler(isPresent: true)
- }
- func testUnrecognisedALPNCloses() {
- self.setUp(tls: true, requireALPN: true)
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: "unsupported")
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.channel.embeddedEventLoop.run()
- assertThat(try self.channel.closeFuture.wait(), .doesNotThrow())
- }
- func testNoNegotiatedProtocolCloses() {
- self.setUp(tls: true, requireALPN: true)
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: nil)
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.channel.embeddedEventLoop.run()
- assertThat(try self.channel.closeFuture.wait(), .doesNotThrow())
- }
- func testNoNegotiatedProtocolFallbackToBytesWhenALPNNotRequired() throws {
- self.setUp(tls: true, requireALPN: false)
- // Require ALPN is disabled, so this is a no-op.
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: nil)
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- // Configure via bytes.
- let bytes = ByteBuffer(staticString: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
- assertThat(try self.channel.writeInbound(bytes), .doesNotThrow())
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- #endif // canImport(NIOSSL)
- func testHTTP2SetupViaBytes() {
- self.setUp(tls: false)
- let bytes = ByteBuffer(staticString: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
- assertThat(try self.channel.writeInbound(bytes), .doesNotThrow())
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- func testHTTP2SetupViaBytesDripFed() {
- self.setUp(tls: false)
- var bytes = ByteBuffer(staticString: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
- var head = bytes.readSlice(length: bytes.readableBytes - 1)!
- let tail = bytes.readSlice(length: 1)!
- while let slice = head.readSlice(length: 1) {
- assertThat(try self.channel.writeInbound(slice), .doesNotThrow())
- self.assertConfigurator(isPresent: true)
- self.assertHTTP2Handler(isPresent: false)
- }
- // Final byte.
- assertThat(try self.channel.writeInbound(tail), .doesNotThrow())
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- func testHTTP1Dot1SetupViaBytes() {
- self.setUp(tls: false)
- let bytes = ByteBuffer(staticString: "GET http://www.foo.bar HTTP/1.1\r\n")
- assertThat(try self.channel.writeInbound(bytes), .doesNotThrow())
- self.assertConfigurator(isPresent: false)
- self.assertGRPCWebToHTTP2Handler(isPresent: true)
- }
- func testHTTP1Dot1SetupViaBytesDripFed() {
- self.setUp(tls: false)
- var bytes = ByteBuffer(staticString: "GET http://www.foo.bar HTTP/1.1\r\n")
- var head = bytes.readSlice(length: bytes.readableBytes - 1)!
- let tail = bytes.readSlice(length: 1)!
- while let slice = head.readSlice(length: 1) {
- assertThat(try self.channel.writeInbound(slice), .doesNotThrow())
- self.assertConfigurator(isPresent: true)
- self.assertGRPCWebToHTTP2Handler(isPresent: false)
- }
- // Final byte.
- assertThat(try self.channel.writeInbound(tail), .doesNotThrow())
- self.assertConfigurator(isPresent: false)
- self.assertGRPCWebToHTTP2Handler(isPresent: true)
- }
- func testUnexpectedInputClosesEventuallyWhenDripFed() {
- self.setUp(tls: false)
- var bytes = ByteBuffer(repeating: UInt8(ascii: "a"), count: 2048)
- while let slice = bytes.readSlice(length: 1) {
- assertThat(try self.channel.writeInbound(slice), .doesNotThrow())
- self.assertConfigurator(isPresent: true)
- self.assertHTTP2Handler(isPresent: false)
- self.assertGRPCWebToHTTP2Handler(isPresent: false)
- }
- self.channel.embeddedEventLoop.run()
- assertThat(try self.channel.closeFuture.wait(), .doesNotThrow())
- }
- func testReadsAreUnbufferedAfterConfiguration() throws {
- self.setUp(tls: false)
- var bytes = ByteBuffer(staticString: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
- // A SETTINGS frame MUST follow the connection preface. Append one so that the HTTP/2 handler
- // responds with its initial settings (and we validate that we forward frames once configuring).
- let emptySettingsFrameBytes: [UInt8] = [
- 0x00, 0x00, 0x00, // 3-byte payload length (0 bytes)
- 0x04, // 1-byte frame type (SETTINGS)
- 0x00, // 1-byte flags (none)
- 0x00, 0x00, 0x00, 0x00, // 4-byte stream identifier
- ]
- bytes.writeBytes(emptySettingsFrameBytes)
- // Do the setup.
- assertThat(try self.channel.writeInbound(bytes), .doesNotThrow())
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- // We expect the server to respond with a SETTINGS frame now.
- let ioData = try channel.readOutbound(as: IOData.self)
- switch ioData {
- case var .some(.byteBuffer(buffer)):
- if let frame = buffer.readBytes(length: 9) {
- // Just check it's a SETTINGS frame.
- assertThat(frame[3], .is(0x04))
- } else {
- XCTFail("Expected more bytes")
- }
- default:
- XCTFail("Expected ByteBuffer but got \(String(describing: ioData))")
- }
- }
- #if canImport(NIOSSL)
- func testALPNIsPreferredOverBytes() throws {
- self.setUp(tls: true, requireALPN: true)
- // Write in an HTTP/1 request line. This should just be buffered.
- let bytes = ByteBuffer(staticString: "GET http://www.foo.bar HTTP/1.1\r\n")
- assertThat(try self.channel.writeInbound(bytes), .doesNotThrow())
- self.assertConfigurator(isPresent: true)
- self.assertHTTP2Handler(isPresent: false)
- self.assertGRPCWebToHTTP2Handler(isPresent: false)
- // Now configure HTTP/2 with ALPN. This should be used to configure the pipeline.
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: "h2")
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.assertConfigurator(isPresent: false)
- self.assertGRPCWebToHTTP2Handler(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- func testALPNFallbackToAlreadyBufferedBytes() throws {
- self.setUp(tls: true, requireALPN: false)
- // Write in an HTTP/2 connection preface. This should just be buffered.
- let bytes = ByteBuffer(staticString: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
- assertThat(try self.channel.writeInbound(bytes), .doesNotThrow())
- self.assertConfigurator(isPresent: true)
- self.assertHTTP2Handler(isPresent: false)
- // Complete the handshake with no protocol negotiated, we should fallback to the buffered bytes.
- let event = TLSUserEvent.handshakeCompleted(negotiatedProtocol: nil)
- self.channel.pipeline.fireUserInboundEventTriggered(event)
- self.assertConfigurator(isPresent: false)
- self.assertHTTP2Handler(isPresent: true)
- }
- #endif // canImport(NIOSSL)
- }
|