From 1da0964a22a31a586984d1d7a459fdfb87b210fc Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Mon, 9 Mar 2020 13:20:10 +0100 Subject: [PATCH] =?UTF-8?q?Removed=20=E2=80=9CConnection=E2=80=9D=20header?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/MockServer/main.swift | 5 ++++- Sources/SwiftAwsLambda/HttpClient.swift | 30 ++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Sources/MockServer/main.swift b/Sources/MockServer/main.swift index 19c4f282..add024f5 100644 --- a/Sources/MockServer/main.swift +++ b/Sources/MockServer/main.swift @@ -125,7 +125,10 @@ internal final class HTTPHandler: ChannelInboundHandler { func writeResponse(context: ChannelHandlerContext, status: HTTPResponseStatus, headers: [(String, String)]? = nil, body: String? = nil) { var headers = HTTPHeaders(headers ?? []) headers.add(name: "Content-Length", value: "\(body?.utf8.count ?? 0)") - headers.add(name: "Connection", value: self.keepAlive ? "keep-alive" : "close") + if !self.keepAlive { + // We only need to add a "Connection" header if we really want to close the connection + headers.add(name: "Connection", value: "close") + } let head = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: status, headers: headers) context.write(wrapOutboundOut(.head(head))).whenFailure { error in diff --git a/Sources/SwiftAwsLambda/HttpClient.swift b/Sources/SwiftAwsLambda/HttpClient.swift index bfd95bcb..18bf9377 100644 --- a/Sources/SwiftAwsLambda/HttpClient.swift +++ b/Sources/SwiftAwsLambda/HttpClient.swift @@ -111,6 +111,7 @@ internal final class HTTPClient { } internal struct Response: Equatable { + public var version: HTTPVersion public var status: HTTPResponseStatus public var headers: HTTPHeaders public var body: ByteBuffer? @@ -150,7 +151,20 @@ private final class HTTPHandler: ChannelDuplexHandler { if let body = request.body { head.headers.add(name: "Content-Length", value: String(body.readableBytes)) } - head.headers.add(name: "Connection", value: self.keepAlive ? "keep-alive" : "close") + // We don't add a "Connection" header here if we want to keep the connection open, + // HTTP/1.1 defines specifies the following in RFC 2616, Section 8.1.2.1: + // + // An HTTP/1.1 server MAY assume that a HTTP/1.1 client intends to + // maintain a persistent connection unless a Connection header including + // the connection-token "close" was sent in the request. If the server + // chooses to close the connection immediately after sending the + // response, it SHOULD send a Connection header including the + // connection-token close. + // + // See also UnaryHandler.channelRead below. + if !self.keepAlive { + head.headers.add(name: "Connection", value: "close") + } context.write(self.wrapOutboundOut(HTTPClientRequestPart.head(head))).flatMap { _ -> EventLoopFuture in if let body = request.body { @@ -184,10 +198,10 @@ private final class HTTPHandler: ChannelDuplexHandler { case .end: switch self.readState { case .head(let head): - context.fireChannelRead(wrapInboundOut(HTTPClient.Response(status: head.status, headers: head.headers, body: nil))) + context.fireChannelRead(wrapInboundOut(HTTPClient.Response(version: head.version, status: head.status, headers: head.headers, body: nil))) self.readState = .idle case .body(let head, let body): - context.fireChannelRead(wrapInboundOut(HTTPClient.Response(status: head.status, headers: head.headers, body: body))) + context.fireChannelRead(wrapInboundOut(HTTPClient.Response(version: head.version, status: head.status, headers: head.headers, body: body))) self.readState = .idle default: preconditionFailure("invalid read state \(self.readState)") @@ -238,8 +252,14 @@ private final class UnaryHandler: ChannelDuplexHandler { guard let pending = self.pending else { preconditionFailure("invalid state, no pending request") } - let serverKeepAlive = response.headers.first(name: "connection")?.lowercased() == "keep-alive" - if !self.keepAlive || !serverKeepAlive { + + // As defined in RFC 2616 Section 8.1.2: + // [...] unless otherwise indicated, the client SHOULD assume + // that the server will maintain a persistent connection, even + // after error responses from the server. + let serverCloseConnection = response.headers.first(name: "connection")?.lowercased() == "close" + + if !self.keepAlive || serverCloseConnection || response.version != .init(major: 1, minor: 1) { pending.promise.futureResult.whenComplete { _ in _ = context.channel.close() }