profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/artemredkin/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Artem Redkin artemredkin Apple London, UK

swift-server/async-http-client 481

HTTP client library built on SwiftNIO

artemredkin/async-http-client 0

HTTP client library built on SwiftNIO

artemredkin/sswg 0

Swift Server Working Group (SSWG)

artemredkin/swift-crypto 0

Open-source implementation of a substantial portion of the API of Apple CryptoKit suitable for use on Linux platforms.

artemredkin/swift-nio 0

Event-driven network application framework for high performance protocol servers & clients, non-blocking.

artemredkin/swift-nio-extras 0

Useful code around SwiftNIO.

artemredkin/swift-nio-http2 0

HTTP/2 support for SwiftNIO

artemredkin/swift-nio-ssh 0

SwiftNIO SSH is a programmatic implementation of SSH using SwiftNIO

artemredkin/swift-nio-ssl 0

TLS Support for SwiftNIO, based on BoringSSL.

push eventartemredkin/async-http-client

Cory Benfield

commit sha e4fded76acd97d78b70f350b74f5151dae703b3e

Better backpressure management. (#352) Motivation: Users of the HTTPClientResponseDelegate expect that the event loop futures returned from didReceiveHead and didReceiveBodyPart can be used to exert backpressure. To be fair to them, they somewhat can. However, the TaskHandler has a bit of a misunderstanding about how NIO backpressure works, and does not correctly manage the buffer of inbound data. The result of this misunderstanding is that multiple calls to didReceiveBodyPart and didReceiveHead can be outstanding at once. This would likely lead to severe bugs in most delegates, as they do not expect it. We should make things work the way delegate implementers believe it works. Modifications: - Added a buffer to the TaskHandler to avoid delivering data that the delegate is not ready for. - Added a new "pending close" state that keeps track of a state where the TaskHandler has received .end but not yet delivered it to the delegate. This allows better error management. - Added some more tests. - Documented our backpressure commitments. Result: Better respect for backpressure. Resolves #348

view details

David Evans

commit sha f3521033efcf02027367197986afbbf6808a1ed8

Fix tests (#356)

view details

tomer doron

commit sha abac00add8f9cdee6271a7f38fc6e626ef2694c0

add 5.4 docker setup for CI (#360) motivation: 5.4 is out! changes: * update Dockerfile handling of rubygems * add docker compose setup for ubuntu 20.04 and 5.4 toolchain

view details

tomer doron

commit sha b5b04acccd9f1412194c050e76c3dad160894cbe

add 5.4 docker setup for CI (#361) motivation: test with nightly toolchain changes: add docker compose setup for ubuntu 20.04 and nightly toolchain

view details

Mads Odgaard

commit sha ca722d83378d3b6d405f25a672c995bca677873b

Support request specific TLS configuration (#358) Adds support for request-specific TLS configuration: Request(url: "https://webserver.com", tlsConfiguration: .forClient())

view details

Johannes Weiss

commit sha e2d03ffb32880269e548dc702bac19c9978160fb

cache NIOSSLContext (saves 27k allocs per conn) (#362) Motivation: At the moment, AHC assumes that creating a `NIOSSLContext` is both cheap and doesn't block. Neither of these two assumptions are true. To create a `NIOSSLContext`, BoringSSL will have to read a lot of certificates in the trust store (on disk) which require a lot of ASN1 parsing and much much more. On my Ubuntu test machine, creating one `NIOSSLContext` is about 27,000 allocations!!! To make it worse, AHC allocates a fresh `NIOSSLContext` for _every single connection_, whether HTTP or HTTPS. Yes, correct. Modification: - Cache NIOSSLContexts per TLSConfiguration in a LRU cache - Don't get an NIOSSLContext for HTTP (plain text) connections Result: New connections should be _much_ faster in general assuming that you're not using a different TLSConfiguration for every connection.

view details

Johannes Weiss

commit sha 06daedfbbd53863f2179c6e3d65eae97dada207b

TLS on Darwin: Add explainer that MTELG supports all options

view details

Adam Fowler

commit sha 9cdf8a01e55a8a66b2210d0a1248060f0964dcd9

Generate trust roots SecCertificate for Transport Services (#350) This PR is a result of another #321. In that PR I provided an alternative structure to TLSConfiguration for when connecting with Transport Services. In this one I construct the NWProtocolTLS.Options from TLSConfiguration. It does mean a little more work for whenever we make a connection, but having spoken to @weissi he doesn't seem to think that is an issue. Also there is no method to create a SecIdentity at the moment. We need to generate a pkcs#12 from the certificate chain and private key, which can then be used to create the SecIdentity. This should resolve #292

view details

Johannes Weiss

commit sha 8ccba7328d178ac05a1a9803cf3f2c6660d2f826

SSLContextCache: use DispatchQueue instead of NIOThreadPool (#368) Motivation: In the vast majority of cases, we'll only ever create one and only one `NIOSSLContext`. It's therefore wasteful to keep around a whole thread doing nothing just for that. A `DispatchQueue` is absolutely fine here. Modification: Run the `NIOSSLContext` creation on a `DispatchQueue` instead. Result: Fewer threads hanging around.

view details

Johannes Weiss

commit sha cddb69d7d4980c428ea6d5eafb32fb76c151cda8

fix Swift Package Index build (#369) Co-authored-by: Johannes Weiss <johannes@jweiss.io>

view details

Johannes Weiss

commit sha 4ca666dbdb0e848f46fdf510c11d6d55ea61a8db

publish security process (#370)

view details

Fabian Fett

commit sha 210b54f0c05dabe087f3ae0bfd86b3e19b75e729

Fixes the DelayOnHeadDelegate test utility (#374) Currently the DelayOnHeadDelegate test utility depends on correct timings. Right now, if a request is slower than 50ms in `testErrorAfterCloseWhileBackpressureExerted` the test will fail, since the backpressure promise is failed, before a head was received. This pr fixes this, by giving the user a callback, when the head was received.

view details

David Evans

commit sha 3fd0658dd91864099481b23740afd1757d1f0634

Implement SOCKS proxy functionality (#375) Add a new Proxy type to enable a HTTPClient to send requests via a SOCKSv5 Proxy server.

view details

David Evans

commit sha 711622b473adc48748225734238a8be86fdae2de

Fix test for misbehaving server (#379) The misbehaving SOCKS server test was previously passing, but not for the right reason. The invalid data we were sending was never making it off the server thanks to the SOCKSServerHandshakeHandler. In fact, as long as SOCKSServerHandshakeHandler is present, it isn't possible to send invalid data. So to test the client now we have to add a completely new handler, which exists only to write some nonsense bytes. The test still passes, but for the right reason, and we now assert the type of error the client throws too.

view details

David Evans

commit sha 92bcf49c96ea59684c9f927353ba26e527dfc82c

Make Swift 5.2 the minimum required version (#378) We're applying this change across all of the NIO family of projects. I've removed the docker files for earlier versions, and set the tools version. I've also added a brief note inside the contributing guide.

view details

Fabian Fett

commit sha 132dc3e3f7d9713f4377bf8ac00a6677a4ad8251

Make `testSSLHandshakeErrorPropagation` less flaky (#380) ### Motivation - The `testSSLHandshakeErrorPropagation` has a very short connect timeout (100ms). The reason for this is, that a failing TLS handshake manifest in NIOTS as a connect timeout and we don’t want to wait for the timeout for to long in our tests. - Using a the NIOSSL however we expect a proper NIOSSLError. Sometimes though the connection is not setup fast enough (because of the very short 100ms connect timeout), that we get a connectionTimeout error instead. ### Modifications - Setting the short connect timeout to 100ms when using NIOTS only. This will make sure we get the proper NIOSSLError when using NIOSSL

view details

David Evans

commit sha 102b7e4bced0e1e85a05542347f91d198a03231e

Update NIO family dependencies to 5.2+ versions and fix deprecations (#381) Updated: NIO NIOSSL NIO Extras NIOTS Also fix TLSConfiguration.forClient() warnings by converting to TLSConfiguration.makeClientConfiguration(). Also the same for forServer().

view details

Fabian Fett

commit sha af837edfcbc471953c77bb29d46201b68f3298b0

Add http/2 support to HTTPBin (#382) - Add `swift-nio-http2` as a dependency to the Package.swift (only used in the test target so far). - Remove unused initialization options for `HTTPBin` - The initialization options `ssl: Bool = false, compress: Bool = false, refusesConnections: Bool = false` move into a `Mode` enum. The mode might be `.http1_1(ssl: Bool, compress: Bool)`, `.http2(compress: Bool)`, or `.refuse` - Added support for `http/2` (not used in any tests yet, however I verified the support with curl locally.) - Nearly all channel pipeline modifications in `HTTPBin` are synchronous now. - HTTPBin's request handler is configurable. This allows testing of more esoteric cases without having to create a new server every time. The default `HTTPBin` continues to use the `HTTPBinHandler`.

view details

Fabian Fett

commit sha 8e4d51908dd49272667126403bf977c5c503f78f

Refactor Channel creation (#377) - The connection creation logic has been refactored into a number of smaller methods that can be combined - Connection creation now has a logical home. It is moved from `Utils.swift` into a `ConnectionFactory` - There are explicit `ChannelHandlers` that are used for connection creation: - `TLSEventsHandler` got its own file and unit tests - `HTTP1ProxyConnectHandler` got its own file and unit tests - `SOCKSEventsHandler` got its own file and unit tests - Some small things are already part of this pr that will get their context later. For example: - `HTTPConnectionPool` is added as a namespace to not cause major renames in follow up PRs - `HTTPConnectionPool.Connection.ID` and its generator were added now. (This will be used later to identify a connection during its lifetime) - the file `HTTPConnectionPool+Manager` was added to give `HTTPConnectionPool.Connection.ID.Generator` already its final destination.

view details

Fabian Fett

commit sha cd7c8048a8359cee663f21fe2db353a3771e2411

Add `HTTPRequestStateMachine` (#386) This commit adds an explicit `HTTPRequestStateMachine`. It is intended to be used in two instances in the future: 1. As a substate machine for a `HTTP1ConnectionStateMachine`, when the connection is in a request. 2. As a state machine for a `HTTP2RequestHandler` that operates on a single http2 stream created by the multiplexer. The new code paths are not triggered today, but will be enabled in the future.

view details

push time in 23 days

push eventartemredkin/swift-nio-ssh

Cory Benfield

commit sha 66ad842080d53049482c1b49ff740c61ba063848

Docker setup for main nightlies (#78)

view details

George Barnett

commit sha 332242685f5e1c747288213ef88e43dd85555197

Add SECURITY.md (#79)

view details

George Barnett

commit sha 5d1cfdeace9502ff33c3fba3ad3d6e56533e679d

Add support from synchronous options (#80) Motivation: NIO 2.27.0 added a customisation point to `Channel` to allow options to be fetched synchronously if the caller is on the appropriate event loop. Modifications: - Add synchronous options to SSHChildChannel - Promote event loop assertions to preconditions Result: Callers can fetch channel options synchronously on 'SSHChildChannel's.

view details

Johannes Weiss

commit sha 06f2e0e75563d65e7992603802d34c19e8cc3421

docker: use 5.4 release instead of nightly (#81)

view details

Johannes Weiss

commit sha f4fdb9e7d3a2a3dc7194e803b43051b9dfe6ed73

add Swift 5.5 job (#82)

view details

Cory Benfield

commit sha aab4ce5b7b25f1dbfce53912dc2299ffbab182b5

Add a benchmark for SSH throughput. (#83) Motivation: We should have more benchmarks. Modifications: Add a throughput benchmark that just sends a bunch of data through. Result: We'll have more data.

view details

Cory Benfield

commit sha 323b3ef3e82ce92eb44376cc631082c2174660d2

Use the ContiguousBytes functions for setting data (#84) Motivation: We should use setContiguousBytes instead of setBytes to avoid hitting the slow path. Modifications: - Use setContiguousBytes. Result: Speedy speedy

view details

David Evans

commit sha 63db5f1c16463b5d9e3f7493b88c57b6888f708f

Remove support for Swift 5.1 (#87) Changes made: Removed CI config for 5.1 Changed 5.2 to be based on 16.04 so we have some coverage there Converted Package.swift to the latest syntax Added a description to supported Swift versions in the README Disabled TSan as it was causing a headache.

view details

David Evans

commit sha bc1a91daf3ae35134fbca5655ed221668d0369d3

Update NIO to 2.30.0 (#88)

view details

Artem Redkin

commit sha 5a28f0d22e2057b09243f8a7e214938698a4b8c3

fixes compilation warning (#90) Motivation: Method Channel.removeHandlers(channel: Channel) was deprecated in 2.32, we should use recommended method instead. Modifications: - Bump swift-nio version to 2.32 to ensue that new methods are available - Use new method to remove all handlers

view details

push time in 25 days

delete branch artemredkin/swift-nio-ssh

delete branch : fixes_compilation_warning

delete time in 25 days

PR opened apple/swift-nio-ssh

Reviewers
fixes compilation warning

Motivation: Method Channel.removeHandlers(channel: Channel) was deprecated in 2.32, we should use recommended method instead.

Modifications:

  • Bump swift-nio version to 2.32 to ensue that new methods are available
  • Use new method to remove all handlers
+3 -3

0 comment

2 changed files

pr created time in 25 days

create barnchartemredkin/swift-nio-ssh

branch : fixes_compilation_warning

created branch time in 25 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentswift-server/async-http-client

Refactor Channel creation

+//===----------------------------------------------------------------------===//+//+// 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+//+//===----------------------------------------------------------------------===//++import Logging+import NIO+import NIOHTTP1+import NIOSSL+import NIOTLS+#if canImport(Network)+    import NIOTransportServices+#endif++extension HTTPConnectionPool {+    enum NegotiatedProtocol {+        case http1_1(Channel)+        case http2_0(Channel)+    }++    final class ConnectionFactory {+        let key: ConnectionPool.Key+        let clientConfiguration: HTTPClient.Configuration+        let tlsConfiguration: TLSConfiguration+        let sslContextCache: SSLContextCache++        init(key: ConnectionPool.Key,+             tlsConfiguration: TLSConfiguration?,+             clientConfiguration: HTTPClient.Configuration,+             sslContextCache: SSLContextCache) {+            self.key = key+            self.clientConfiguration = clientConfiguration+            self.sslContextCache = sslContextCache+            self.tlsConfiguration = tlsConfiguration ?? clientConfiguration.tlsConfiguration ?? .forClient()+        }+    }+}++extension HTTPConnectionPool.ConnectionFactory {+    func makeBestChannel(connectionID: HTTPConnectionPool.Connection.ID, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {+        if self.key.scheme.isProxyable, let proxy = self.clientConfiguration.proxy {+            return self.makeHTTPProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)+        } else {+            return self.makeChannel(eventLoop: eventLoop, logger: logger)+        }+    }++    private func makeChannel(eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {+        switch self.key.scheme {+        case .http, .http_unix, .unix:+            return self.makePlainChannel(eventLoop: eventLoop).map { ($0, .http1_1) }+        case .https, .https_unix:+            return self.makeTLSChannel(eventLoop: eventLoop, logger: logger).map {+                (channel, negotiated) -> (Channel, HTTPVersion) in+                let version = negotiated == "h2" ? HTTPVersion.http2 : HTTPVersion.http1_1+                return (channel, version)+            }+        }+    }++    private func makePlainChannel(eventLoop: EventLoop) -> EventLoopFuture<Channel> {+        let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)++        switch self.key.scheme {+        case .http:+            return bootstrap.connect(host: self.key.host, port: self.key.port)+        case .http_unix, .unix:+            return bootstrap.connect(unixDomainSocketPath: self.key.unixPath)+        case .https, .https_unix:+            preconditionFailure("Unexpected schema")+        }+    }++    private func makeHTTPProxyChannel(+        _ proxy: HTTPClient.Configuration.Proxy,+        connectionID: HTTPConnectionPool.Connection.ID,+        eventLoop: EventLoop,+        logger: Logger+    ) -> EventLoopFuture<(Channel, HTTPVersion)> {+        // A proxy connection starts with a plain text connection to the proxy server. After+        // the connection has been established with the proxy server, the connection might be+        // upgraded to TLS before we send our first request.+        let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)+        return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in+            let connectPromise = channel.eventLoop.makePromise(of: Void.self)++            let encoder = HTTPRequestEncoder()+            let decoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes))+            let proxyHandler = HTTP1ProxyConnectHandler(+                targetHost: self.key.host,+                targetPort: self.key.port,+                proxyAuthorization: proxy.authorization,+                connectPromise: connectPromise+            )++            do {+                try channel.pipeline.syncOperations.addHandler(encoder)+                try channel.pipeline.syncOperations.addHandler(decoder)+                try channel.pipeline.syncOperations.addHandler(proxyHandler)+            } catch {+                return channel.eventLoop.makeFailedFuture(error)+            }++            return connectPromise.futureResult.flatMap {+                channel.pipeline.removeHandler(proxyHandler).flatMap {+                    channel.pipeline.removeHandler(decoder).flatMap {+                        channel.pipeline.removeHandler(encoder)+                    }+                }+            }.flatMap { () -> EventLoopFuture<(Channel, HTTPVersion)> in+                switch self.key.scheme {+                case .unix, .http_unix, .https_unix:+                    preconditionFailure("Unexpected scheme. Not supported for proxy!")+                case .http:+                    return channel.eventLoop.makeSucceededFuture((channel, .http1_1))+                case .https:+                    var tlsConfig = self.tlsConfiguration+                    // since we can support h2, we need to advertise this in alpn+                    tlsConfig.applicationProtocols = ["http/1.1" /* , "h2" */ ]+                    let tlsEventHandler = TLSEventsHandler()++                    let sslContextFuture = self.sslContextCache.sslContext(+                        tlsConfiguration: tlsConfig,+                        eventLoop: channel.eventLoop,+                        logger: logger+                    )++                    return sslContextFuture.flatMap { sslContext -> EventLoopFuture<String?> in+                        do {+                            let sslHandler = try NIOSSLClientHandler(+                                context: sslContext,+                                serverHostname: self.key.host+                            )+                            try channel.pipeline.syncOperations.addHandler(sslHandler)+                            try channel.pipeline.syncOperations.addHandler(tlsEventHandler)+                            return tlsEventHandler.tlsEstablishedFuture+                        } catch {+                            return channel.eventLoop.makeFailedFuture(error)+                        }+                    }.flatMap { negotiated -> EventLoopFuture<(Channel, HTTPVersion)> in+                        channel.pipeline.removeHandler(tlsEventHandler).map {+                            switch negotiated {+                            case "h2":+                                return (channel, .http2)+                            default:+                                return (channel, .http1_1)+                            }+                        }+                    }+                }+            }+        }+    }++    private func makePlainBootstrap(eventLoop: EventLoop) -> NIOClientTCPBootstrapProtocol {+        #if canImport(Network)+            if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) {+                return tsBootstrap+                    .addTimeoutIfNeeded(self.clientConfiguration.timeout)+                    .channelInitializer { channel in+                        do {+                            try channel.pipeline.syncOperations.addHandler(HTTPClient.NWErrorHandler())

nit: then we should probably return eventLoop.makeSucceededVoidFuture()

fabianfett

comment created time in 3 months

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentswift-server/async-http-client

Refactor Channel creation

+//===----------------------------------------------------------------------===//+//+// 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+//+//===----------------------------------------------------------------------===//++import NIO+import NIOHTTP1++final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHandler {+    typealias OutboundIn = Never+    typealias OutboundOut = HTTPClientRequestPart+    typealias InboundIn = HTTPClientResponsePart++    enum State {+        case initialized(EventLoopPromise<Void>)+        case connectSent(EventLoopPromise<Void>)+        case headReceived(EventLoopPromise<Void>)+        case failed(Error)+        case completed+    }++    private var state: State++    let targetHost: String+    let targetPort: Int+    let proxyAuthorization: HTTPClient.Authorization?++    init(targetHost: String,+         targetPort: Int,+         proxyAuthorization: HTTPClient.Authorization?,+         connectPromise: EventLoopPromise<Void>) {+        self.targetHost = targetHost+        self.targetPort = targetPort+        self.proxyAuthorization = proxyAuthorization++        self.state = .initialized(connectPromise)+    }++    func handlerAdded(context: ChannelHandlerContext) {+        precondition(context.channel.isActive, "Expected to be added to an active channel")++        self.sendConnect(context: context)+    }++    func handlerRemoved(context: ChannelHandlerContext) {+        switch self.state {+        case .failed, .completed:+            break+        case .initialized, .connectSent, .headReceived:+            preconditionFailure("Removing the handler, while connecting seems wrong")+        }+    }++    func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {+        preconditionFailure("We don't support outgoing traffic during HTTP Proxy update.")

was it copied form the old handler? if yes, then ignore my comment

fabianfett

comment created time in 3 months

PullRequestReviewEvent

Pull request review commentswift-server/async-http-client

Refactor Channel creation

+//===----------------------------------------------------------------------===//+//+// 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+//+//===----------------------------------------------------------------------===//++import NIOConcurrencyHelpers++extension HTTPConnectionPool.Connection.ID {+    static var globalGenerator = Generator()++    struct Generator {+        private let atomic: NIOAtomic<Int>

maybe we can make it UInt64, just in case someone will be running it until our sun dies 😆

fabianfett

comment created time in 3 months

PullRequestReviewEvent

Pull request review commentswift-server/async-http-client

Refactor Channel creation

+//===----------------------------------------------------------------------===//+//+// 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+//+//===----------------------------------------------------------------------===//++import Logging+import NIO+import NIOHTTP1+import NIOSSL+import NIOTLS+#if canImport(Network)+    import NIOTransportServices+#endif++extension HTTPConnectionPool {+    enum NegotiatedProtocol {+        case http1_1(Channel)+        case http2_0(Channel)+    }++    final class ConnectionFactory {+        let key: ConnectionPool.Key+        let clientConfiguration: HTTPClient.Configuration+        let tlsConfiguration: TLSConfiguration+        let sslContextCache: SSLContextCache++        init(key: ConnectionPool.Key,+             tlsConfiguration: TLSConfiguration?,+             clientConfiguration: HTTPClient.Configuration,+             sslContextCache: SSLContextCache) {+            self.key = key+            self.clientConfiguration = clientConfiguration+            self.sslContextCache = sslContextCache+            self.tlsConfiguration = tlsConfiguration ?? clientConfiguration.tlsConfiguration ?? .forClient()+        }+    }+}++extension HTTPConnectionPool.ConnectionFactory {+    func makeBestChannel(connectionID: HTTPConnectionPool.Connection.ID, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {+        if self.key.scheme.isProxyable, let proxy = self.clientConfiguration.proxy {+            return self.makeHTTPProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)+        } else {+            return self.makeChannel(eventLoop: eventLoop, logger: logger)+        }+    }++    private func makeChannel(eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {+        switch self.key.scheme {+        case .http, .http_unix, .unix:+            return self.makePlainChannel(eventLoop: eventLoop).map { ($0, .http1_1) }+        case .https, .https_unix:+            return self.makeTLSChannel(eventLoop: eventLoop, logger: logger).map {+                (channel, negotiated) -> (Channel, HTTPVersion) in+                let version = negotiated == "h2" ? HTTPVersion.http2 : HTTPVersion.http1_1+                return (channel, version)+            }+        }+    }++    private func makePlainChannel(eventLoop: EventLoop) -> EventLoopFuture<Channel> {+        let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)++        switch self.key.scheme {+        case .http:+            return bootstrap.connect(host: self.key.host, port: self.key.port)+        case .http_unix, .unix:+            return bootstrap.connect(unixDomainSocketPath: self.key.unixPath)+        case .https, .https_unix:+            preconditionFailure("Unexpected schema")+        }+    }++    private func makeHTTPProxyChannel(+        _ proxy: HTTPClient.Configuration.Proxy,+        connectionID: HTTPConnectionPool.Connection.ID,+        eventLoop: EventLoop,+        logger: Logger+    ) -> EventLoopFuture<(Channel, HTTPVersion)> {+        // A proxy connection starts with a plain text connection to the proxy server. After+        // the connection has been established with the proxy server, the connection might be+        // upgraded to TLS before we send our first request.+        let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)+        return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in+            let connectPromise = channel.eventLoop.makePromise(of: Void.self)++            let encoder = HTTPRequestEncoder()+            let decoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes))+            let proxyHandler = HTTP1ProxyConnectHandler(+                targetHost: self.key.host,+                targetPort: self.key.port,+                proxyAuthorization: proxy.authorization,+                connectPromise: connectPromise+            )++            do {+                try channel.pipeline.syncOperations.addHandler(encoder)+                try channel.pipeline.syncOperations.addHandler(decoder)+                try channel.pipeline.syncOperations.addHandler(proxyHandler)+            } catch {+                return channel.eventLoop.makeFailedFuture(error)+            }++            return connectPromise.futureResult.flatMap {+                channel.pipeline.removeHandler(proxyHandler).flatMap {+                    channel.pipeline.removeHandler(decoder).flatMap {+                        channel.pipeline.removeHandler(encoder)+                    }+                }+            }.flatMap { () -> EventLoopFuture<(Channel, HTTPVersion)> in+                switch self.key.scheme {+                case .unix, .http_unix, .https_unix:+                    preconditionFailure("Unexpected scheme. Not supported for proxy!")+                case .http:+                    return channel.eventLoop.makeSucceededFuture((channel, .http1_1))+                case .https:+                    var tlsConfig = self.tlsConfiguration+                    // since we can support h2, we need to advertise this in alpn+                    tlsConfig.applicationProtocols = ["http/1.1" /* , "h2" */ ]+                    let tlsEventHandler = TLSEventsHandler()++                    let sslContextFuture = self.sslContextCache.sslContext(+                        tlsConfiguration: tlsConfig,+                        eventLoop: channel.eventLoop,+                        logger: logger+                    )++                    return sslContextFuture.flatMap { sslContext -> EventLoopFuture<String?> in+                        do {+                            let sslHandler = try NIOSSLClientHandler(+                                context: sslContext,+                                serverHostname: self.key.host+                            )+                            try channel.pipeline.syncOperations.addHandler(sslHandler)+                            try channel.pipeline.syncOperations.addHandler(tlsEventHandler)+                            return tlsEventHandler.tlsEstablishedFuture+                        } catch {+                            return channel.eventLoop.makeFailedFuture(error)+                        }+                    }.flatMap { negotiated -> EventLoopFuture<(Channel, HTTPVersion)> in+                        channel.pipeline.removeHandler(tlsEventHandler).map {+                            switch negotiated {+                            case "h2":+                                return (channel, .http2)+                            default:+                                return (channel, .http1_1)+                            }+                        }+                    }+                }+            }+        }+    }++    private func makePlainBootstrap(eventLoop: EventLoop) -> NIOClientTCPBootstrapProtocol {+        #if canImport(Network)+            if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) {+                return tsBootstrap+                    .addTimeoutIfNeeded(self.clientConfiguration.timeout)+                    .channelInitializer { channel in+                        do {+                            try channel.pipeline.syncOperations.addHandler(HTTPClient.NWErrorHandler())

wouldn't async operations look cleaner here?

fabianfett

comment created time in 3 months

PullRequestReviewEvent

Pull request review commentswift-server/async-http-client

Refactor Channel creation

+//===----------------------------------------------------------------------===//+//+// 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+//+//===----------------------------------------------------------------------===//++import Logging+import NIO+import NIOHTTP1+import NIOSSL+import NIOTLS+#if canImport(Network)+    import NIOTransportServices+#endif++extension HTTPConnectionPool {+    enum NegotiatedProtocol {+        case http1_1(Channel)+        case http2_0(Channel)+    }++    final class ConnectionFactory {+        let key: ConnectionPool.Key+        let clientConfiguration: HTTPClient.Configuration+        let tlsConfiguration: TLSConfiguration+        let sslContextCache: SSLContextCache++        init(key: ConnectionPool.Key,+             tlsConfiguration: TLSConfiguration?,+             clientConfiguration: HTTPClient.Configuration,+             sslContextCache: SSLContextCache) {+            self.key = key+            self.clientConfiguration = clientConfiguration+            self.sslContextCache = sslContextCache+            self.tlsConfiguration = tlsConfiguration ?? clientConfiguration.tlsConfiguration ?? .forClient()+        }+    }+}++extension HTTPConnectionPool.ConnectionFactory {+    func makeBestChannel(connectionID: HTTPConnectionPool.Connection.ID, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {+        if self.key.scheme.isProxyable, let proxy = self.clientConfiguration.proxy {+            return self.makeHTTPProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)+        } else {+            return self.makeChannel(eventLoop: eventLoop, logger: logger)+        }+    }++    private func makeChannel(eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {+        switch self.key.scheme {+        case .http, .http_unix, .unix:+            return self.makePlainChannel(eventLoop: eventLoop).map { ($0, .http1_1) }+        case .https, .https_unix:+            return self.makeTLSChannel(eventLoop: eventLoop, logger: logger).map {+                (channel, negotiated) -> (Channel, HTTPVersion) in+                let version = negotiated == "h2" ? HTTPVersion.http2 : HTTPVersion.http1_1+                return (channel, version)+            }+        }+    }++    private func makePlainChannel(eventLoop: EventLoop) -> EventLoopFuture<Channel> {+        let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)++        switch self.key.scheme {+        case .http:+            return bootstrap.connect(host: self.key.host, port: self.key.port)+        case .http_unix, .unix:+            return bootstrap.connect(unixDomainSocketPath: self.key.unixPath)+        case .https, .https_unix:+            preconditionFailure("Unexpected schema")+        }+    }++    private func makeHTTPProxyChannel(+        _ proxy: HTTPClient.Configuration.Proxy,+        connectionID: HTTPConnectionPool.Connection.ID,+        eventLoop: EventLoop,+        logger: Logger+    ) -> EventLoopFuture<(Channel, HTTPVersion)> {+        // A proxy connection starts with a plain text connection to the proxy server. After+        // the connection has been established with the proxy server, the connection might be+        // upgraded to TLS before we send our first request.+        let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)+        return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in+            let connectPromise = channel.eventLoop.makePromise(of: Void.self)++            let encoder = HTTPRequestEncoder()+            let decoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes))+            let proxyHandler = HTTP1ProxyConnectHandler(+                targetHost: self.key.host,+                targetPort: self.key.port,+                proxyAuthorization: proxy.authorization,+                connectPromise: connectPromise+            )++            do {+                try channel.pipeline.syncOperations.addHandler(encoder)

why not add all handler at once?

fabianfett

comment created time in 3 months

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentswift-server/async-http-client

Refactor Channel creation

+//===----------------------------------------------------------------------===//+//+// 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+//+//===----------------------------------------------------------------------===//++import NIO+import NIOHTTP1++final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHandler {+    typealias OutboundIn = Never+    typealias OutboundOut = HTTPClientRequestPart+    typealias InboundIn = HTTPClientResponsePart++    enum State {+        case initialized(EventLoopPromise<Void>)+        case connectSent(EventLoopPromise<Void>)+        case headReceived(EventLoopPromise<Void>)+        case failed(Error)+        case completed+    }++    private var state: State++    let targetHost: String+    let targetPort: Int+    let proxyAuthorization: HTTPClient.Authorization?++    init(targetHost: String,+         targetPort: Int,+         proxyAuthorization: HTTPClient.Authorization?,+         connectPromise: EventLoopPromise<Void>) {+        self.targetHost = targetHost+        self.targetPort = targetPort+        self.proxyAuthorization = proxyAuthorization++        self.state = .initialized(connectPromise)+    }++    func handlerAdded(context: ChannelHandlerContext) {+        precondition(context.channel.isActive, "Expected to be added to an active channel")++        self.sendConnect(context: context)+    }++    func handlerRemoved(context: ChannelHandlerContext) {+        switch self.state {+        case .failed, .completed:+            break+        case .initialized, .connectSent, .headReceived:+            preconditionFailure("Removing the handler, while connecting seems wrong")+        }+    }++    func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {+        preconditionFailure("We don't support outgoing traffic during HTTP Proxy update.")

just want to make sure - clients will not be able to trigger this precondition no matter what configuration they use?

fabianfett

comment created time in 3 months