Skip to content
29 changes: 18 additions & 11 deletions Sources/AsyncHTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Channel>
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
}

Expand Down Expand Up @@ -481,12 +488,12 @@ private extension ChannelPipeline {
func addProxyHandler(for request: HTTPClient.Request, decoder: ByteToMessageHandler<HTTPResponseDecoder>, encoder: HTTPRequestEncoder, tlsConfiguration: TLSConfiguration?, proxy: HTTPClient.Configuration.Proxy?) -> EventLoopFuture<Void> {
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)
Expand Down
71 changes: 51 additions & 20 deletions Sources/AsyncHTTPClient/HTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -107,6 +149,7 @@ extension HTTPClient {
}

var redirectState: RedirectState?
let kind: Kind

/// Create HTTP request.
///
Expand All @@ -133,7 +176,6 @@ extension HTTPClient {
///
/// - parameters:
/// - url: Remote `URL`.
/// - version: HTTP version.
/// - method: HTTP method.
/// - headers: Custom HTTP headers.
/// - body: Request body.
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -812,7 +843,7 @@ internal struct RedirectHandler<ResponseType> {
return nil
}

guard HTTPClient.Request.isSchemeSupported(scheme: self.request.scheme) else {
guard self.request.kind.supports(scheme: self.request.scheme) else {
return nil
}

Expand Down
12 changes: 10 additions & 2 deletions Tests/AsyncHTTPClientTests/HTTPClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down