diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 331b5bc98..6f15a2b52 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -281,15 +281,22 @@ public class HTTPClient { bootstrap = bootstrap.connectTimeout(timeout) } - let address = self.resolveAddress(request: request, proxy: self.configuration.proxy) - bootstrap.connect(host: address.host, port: address.port) - .map { channel in - task.setChannel(channel) - } - .flatMap { channel in - channel.writeAndFlush(request) - } - .cascadeFailure(to: task.promise) + let eventLoopChannel: EventLoopFuture + if request.kind == .unixSocket, let baseURL = request.url.baseURL { + eventLoopChannel = bootstrap.connect(unixDomainSocketPath: baseURL.path) + } else { + let address = self.resolveAddress(request: request, proxy: self.configuration.proxy) + eventLoopChannel = bootstrap.connect(host: address.host, port: address.port) + } + + eventLoopChannel.map { channel in + task.setChannel(channel) + } + .flatMap { channel in + channel.writeAndFlush(request) + } + .cascadeFailure(to: task.promise) + return task } @@ -481,12 +488,12 @@ private extension ChannelPipeline { func addProxyHandler(for request: HTTPClient.Request, decoder: ByteToMessageHandler, encoder: HTTPRequestEncoder, tlsConfiguration: TLSConfiguration?, proxy: HTTPClient.Configuration.Proxy?) -> EventLoopFuture { let handler = HTTPClientProxyHandler(host: request.host, port: request.port, authorization: proxy?.authorization, onConnect: { channel in channel.pipeline.removeHandler(decoder).flatMap { - return channel.pipeline.addHandler( + channel.pipeline.addHandler( ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .forwardBytes)), position: .after(encoder) ) }.flatMap { - return channel.pipeline.addSSLHandlerIfNeeded(for: request, tlsConfiguration: tlsConfiguration) + channel.pipeline.addSSLHandlerIfNeeded(for: request, tlsConfiguration: tlsConfiguration) } }) return self.addHandler(handler) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 327e55610..88a790330 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -88,12 +88,54 @@ extension HTTPClient { /// Represent HTTP request. public struct Request { + /// Represent kind of Request + enum Kind { + /// Remote host request. + case host + /// UNIX Domain Socket HTTP request. + case unixSocket + + private static var hostSchemes = ["http", "https"] + private static var unixSchemes = ["unix"] + + init(forScheme scheme: String) throws { + if Kind.host.supports(scheme: scheme) { + self = .host + } else if Kind.unixSocket.supports(scheme: scheme) { + self = .unixSocket + } else { + throw HTTPClientError.unsupportedScheme(scheme) + } + } + + func hostFromURL(_ url: URL) throws -> String { + switch self { + case .host: + guard let host = url.host else { + throw HTTPClientError.emptyHost + } + return host + case .unixSocket: + return "" + } + } + + func supports(scheme: String) -> Bool { + switch self { + case .host: + return Kind.hostSchemes.contains(scheme) + case .unixSocket: + return Kind.unixSchemes.contains(scheme) + } + } + } + /// Request HTTP method, defaults to `GET`. - public let method: HTTPMethod + public var method: HTTPMethod /// Remote URL. - public let url: URL + public var url: URL /// Remote HTTP scheme, resolved from `URL`. - public let scheme: String + public var scheme: String /// Remote host, resolved from `URL`. public let host: String /// Request custom HTTP Headers, defaults to no headers. @@ -107,6 +149,7 @@ extension HTTPClient { } var redirectState: RedirectState? + let kind: Kind /// Create HTTP request. /// @@ -133,7 +176,6 @@ extension HTTPClient { /// /// - parameters: /// - url: Remote `URL`. - /// - version: HTTP version. /// - method: HTTP method. /// - headers: Custom HTTP headers. /// - body: Request body. @@ -146,22 +188,15 @@ extension HTTPClient { throw HTTPClientError.emptyScheme } - guard Request.isSchemeSupported(scheme: scheme) else { - throw HTTPClientError.unsupportedScheme(scheme) - } - - guard let host = url.host else { - throw HTTPClientError.emptyHost - } + self.kind = try Kind(forScheme: scheme) + self.host = try self.kind.hostFromURL(url) - self.method = method + self.redirectState = nil self.url = url + self.method = method self.scheme = scheme - self.host = host self.headers = headers self.body = body - - self.redirectState = nil } /// Whether request will be executed using secure socket. @@ -173,10 +208,6 @@ extension HTTPClient { public var port: Int { return self.url.port ?? (self.useTLS ? 443 : 80) } - - static func isSchemeSupported(scheme: String) -> Bool { - return scheme == "http" || scheme == "https" - } } /// Represent HTTP response. @@ -812,7 +843,7 @@ internal struct RedirectHandler { return nil } - guard HTTPClient.Request.isSchemeSupported(scheme: self.request.scheme) else { + guard self.request.kind.supports(scheme: self.request.scheme) else { return nil } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 33c529330..0f91d3995 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -47,14 +47,21 @@ class HTTPClientTests: XCTestCase { let request2 = try Request(url: "https://someserver.com") XCTAssertEqual(request2.url.path, "") + + let request3 = try Request(url: "unix:///tmp/file") + XCTAssertNil(request3.url.host) + XCTAssertEqual(request3.host, "") + XCTAssertEqual(request3.url.path, "/tmp/file") + XCTAssertEqual(request3.port, 80) + XCTAssertFalse(request3.useTLS) } func testBadRequestURI() throws { XCTAssertThrowsError(try Request(url: "some/path"), "should throw") { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyScheme) } - XCTAssertThrowsError(try Request(url: "file://somewhere/some/path?foo=bar"), "should throw") { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.unsupportedScheme("file")) + XCTAssertThrowsError(try Request(url: "app://somewhere/some/path?foo=bar"), "should throw") { error in + XCTAssertEqual(error as! HTTPClientError, HTTPClientError.unsupportedScheme("app")) } XCTAssertThrowsError(try Request(url: "https:/foo"), "should throw") { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyHost) @@ -63,6 +70,7 @@ class HTTPClientTests: XCTestCase { func testSchemaCasing() throws { XCTAssertNoThrow(try Request(url: "hTTpS://someserver.com:8888/some/path?foo=bar")) + XCTAssertNoThrow(try Request(url: "uNIx:///some/path")) } func testGet() throws {