diff --git a/Sources/CNIONghttp2/empty.c b/Sources/CNIONghttp2/empty.c deleted file mode 100644 index caf2924e..00000000 --- a/Sources/CNIONghttp2/empty.c +++ /dev/null @@ -1,13 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftNIO open source project -// -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// diff --git a/Sources/CNIONghttp2/include/c_nio_nghttp2.h b/Sources/CNIONghttp2/include/c_nio_nghttp2.h index 9711f78a..d90edbdd 100644 --- a/Sources/CNIONghttp2/include/c_nio_nghttp2.h +++ b/Sources/CNIONghttp2/include/c_nio_nghttp2.h @@ -11,9 +11,21 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -#ifndef C_NIO_NGHTTP2_H -#define C_NIO_NGHTTP2_H +#ifndef CNIONGHTTP2_H +#define CNIONGHTTP2_H #include +// There are some shims we need to provide here. +typedef int (*CNIONghttp2_nghttp2_error_callback)(nghttp2_session *session, + const char *msg, + size_t len, + void *user_data); + +void CNIONghttp2_nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, + CNIONghttp2_nghttp2_error_callback error_callback); + +int CNIONghttp2_nghttp2_version_number(void); + #endif diff --git a/Sources/CNIONghttp2/shims.c b/Sources/CNIONghttp2/shims.c new file mode 100644 index 00000000..d6fc6330 --- /dev/null +++ b/Sources/CNIONghttp2/shims.c @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +#include + +// This file provides an implementation of a number of shim functions to +// handle different versions of nghttp2. + +// This shim only works on recent versions of nghttp2, otherwise it does +// nothing. +void CNIONghttp2_nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, + CNIONghttp2_nghttp2_error_callback error_callback) { +#if NGHTTP2_VERSION_NUM >= 0x010900 + return nghttp2_session_callbacks_set_error_callback(cbs, error_callback); +#endif +} + +// This shim turns the macro into something we can see. +int CNIONghttp2_nghttp2_version_number(void) { + return NGHTTP2_VERSION_NUM; +} diff --git a/Sources/NIOHTTP2/NGHTTP2Session.swift b/Sources/NIOHTTP2/NGHTTP2Session.swift index e0ab4a4b..1ecc5187 100644 --- a/Sources/NIOHTTP2/NGHTTP2Session.swift +++ b/Sources/NIOHTTP2/NGHTTP2Session.swift @@ -39,7 +39,7 @@ private func withCallbacks(fn: (OpaquePointer) throws -> T) rethrows -> T { nghttp2_session_callbacks_del(nghttp2Callbacks) } - nghttp2_session_callbacks_set_error_callback(nghttp2Callbacks, errorCallback) + CNIONghttp2_nghttp2_session_callbacks_set_error_callback(nghttp2Callbacks, errorCallback) nghttp2_session_callbacks_set_on_frame_recv_callback(nghttp2Callbacks, onFrameRecvCallback) nghttp2_session_callbacks_set_on_begin_frame_callback(nghttp2Callbacks, onBeginFrameCallback) nghttp2_session_callbacks_set_on_data_chunk_recv_callback(nghttp2Callbacks, onDataChunkRecvCallback) diff --git a/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift b/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift index 446db412..796d622d 100644 --- a/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift +++ b/Tests/NIOHTTP2Tests/SimpleClientServerTests.swift @@ -15,6 +15,7 @@ import XCTest import NIO import NIOHTTP1 +import CNIONghttp2 @testable import NIOHTTP2 /// A channel handler that passes writes through but fires EOF once the first one hits. @@ -275,9 +276,8 @@ class SimpleClientServerTests: XCTestCase { self.serverChannel.writeAndFlush(goAwayFrame, promise: nil) self.interactInMemory(self.clientChannel, self.serverChannel) - // The client should not receive this GOAWAY frame, as it has shut down. - self.clientChannel.assertNoFramesReceived() - + // In some nghttp2 versions the client will receive a GOAWAY, in others + // it will not. There is no meaningful assertion to apply here. // All should be good. self.serverChannel.assertNoFramesReceived() XCTAssertNoThrow(try self.clientChannel.finish()) @@ -969,9 +969,8 @@ class SimpleClientServerTests: XCTestCase { // The data frame write should have exploded. nghttp2 synthesises an error code for this. XCTAssertEqual((writeError as? NIOHTTP2Errors.StreamClosed)?.streamID, clientStreamID) - // No other frames should be emitted. - self.clientChannel.assertNoFramesReceived() - self.serverChannel.assertNoFramesReceived() + // No other frames should be emitted, but we're ok if they were: depending on performance + // we may see a WINDOW_UPDATE or two here depending on timings. XCTAssertNoThrow(try self.clientChannel.finish()) XCTAssertNoThrow(try self.serverChannel.finish()) } @@ -1000,7 +999,18 @@ class SimpleClientServerTests: XCTestCase { let respFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(responseHeaders)) var respTrailersFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(trailers)) respTrailersFrame.flags.insert(.endStream) - try self.assertFramesRoundTrip(frames: [respFrame, respTrailersFrame], sender: self.serverChannel, receiver: self.clientChannel) + + // v1.11.0 onwards don't send a 0-length DATA frame here, but earlier ones do. We send it explicitly to get + // the output to match on all platforms. + let expectedFrames: [HTTP2Frame] + if CNIONghttp2_nghttp2_version_number() < 0x011100 { + let emptyDataFrame = HTTP2Frame(streamID: serverStreamID, payload: .data(.byteBuffer(self.serverChannel.allocator.buffer(capacity: 0)))) + expectedFrames = [respFrame, emptyDataFrame, respTrailersFrame] + } else { + expectedFrames = [respFrame, respTrailersFrame] + } + + try self.assertFramesRoundTrip(frames: expectedFrames, sender: self.serverChannel, receiver: self.clientChannel) XCTAssertNoThrow(try self.clientChannel.finish()) XCTAssertNoThrow(try self.serverChannel.finish()) @@ -1026,8 +1036,15 @@ class SimpleClientServerTests: XCTestCase { let serverStreamID = try self.assertFramesRoundTrip(frames: [reqFrame, reqBodyFrame], sender: self.clientChannel, receiver: self.serverChannel).first!.streamID - // Now we can send the next trailers. - XCTAssertNoThrow(try self.assertFramesRoundTrip(frames: [trailerFrame], sender: self.clientChannel, receiver: self.serverChannel)) + // Now we can send the next trailers. Again, old versions of nghttp2 send an empty data frame here too. + var expectedFrames: [HTTP2Frame] + if CNIONghttp2_nghttp2_version_number() < 0x011100 { + let emptyDataFrame = HTTP2Frame(streamID: clientStreamID, payload: .data(.byteBuffer(self.clientChannel.allocator.buffer(capacity: 0)))) + expectedFrames = [emptyDataFrame, trailerFrame] + } else { + expectedFrames = [trailerFrame] + } + XCTAssertNoThrow(try self.assertFramesRoundTrip(frames: expectedFrames, sender: self.clientChannel, receiver: self.serverChannel)) // Let's send a quick response back. This response should also contain trailers. let responseHeaders = HTTPHeaders([(":status", "200"), ("content-length", "0")]) @@ -1035,7 +1052,14 @@ class SimpleClientServerTests: XCTestCase { var respTrailersFrame = HTTP2Frame(streamID: serverStreamID, payload: .headers(trailers)) respTrailersFrame.flags.insert(.endStream) XCTAssertNoThrow(try self.assertFramesRoundTrip(frames: [respFrame], sender: self.serverChannel, receiver: self.clientChannel)) - XCTAssertNoThrow(try self.assertFramesRoundTrip(frames: [respTrailersFrame], sender: self.serverChannel, receiver: self.clientChannel)) + + if CNIONghttp2_nghttp2_version_number() < 0x011100 { + let emptyDataFrame = HTTP2Frame(streamID: serverStreamID, payload: .data(.byteBuffer(self.serverChannel.allocator.buffer(capacity: 0)))) + expectedFrames = [emptyDataFrame, respTrailersFrame] + } else { + expectedFrames = [respTrailersFrame] + } + XCTAssertNoThrow(try self.assertFramesRoundTrip(frames: expectedFrames, sender: self.serverChannel, receiver: self.clientChannel)) XCTAssertNoThrow(try self.clientChannel.finish()) XCTAssertNoThrow(try self.serverChannel.finish()) diff --git a/docker/docker-compose.ubuntu-1404.403.yaml b/docker/docker-compose.ubuntu-1404.403.yaml deleted file mode 100644 index a1597de5..00000000 --- a/docker/docker-compose.ubuntu-1404.403.yaml +++ /dev/null @@ -1,15 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-nio-http2:ubuntu-14.04-4.0.3 - build: - args: - ubuntu_version : "14.04" - swift_version : "4.0.3" - install_curl_from_source: "true" - install_nghttp2_from_source: "true" - - test: - image: swift-nio-http2:ubuntu-14.04-4.0.3 diff --git a/docker/docker-compose.ubuntu-1604.403.yaml b/docker/docker-compose.ubuntu-1604.403.yaml deleted file mode 100644 index 8b94b376..00000000 --- a/docker/docker-compose.ubuntu-1604.403.yaml +++ /dev/null @@ -1,14 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-nio-http2:ubuntu-16.04-4.0.3 - build: - args: - ubuntu_version : "16.04" - swift_version : "4.0.3" - install_nghttp2_from_source: "true" - - test: - image: swift-nio-http2:ubuntu-16.04-4.0.3 diff --git a/docker/docker-compose.ubuntu-1604.41.yaml b/docker/docker-compose.ubuntu-1604.41.yaml index 1e2ee179..d17b3f52 100644 --- a/docker/docker-compose.ubuntu-1604.41.yaml +++ b/docker/docker-compose.ubuntu-1604.41.yaml @@ -8,7 +8,6 @@ services: args: ubuntu_version : "16.04" swift_version : "4.1" - install_nghttp2_from_source: "true" test: image: swift-nio-http2:ubuntu-16.04-4.1