Skip to content

Commit e90f5fd

Browse files
krzyzanowskimweissi
authored andcommitted
Support UNIX Domain Sockets (#151)
Adds support for UNIX Domain Socket requests. Usage: ``` let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) let socketURL = URL(string: "unix:///var/run/docker.sock")! let req = try HTTPClient.Request(url: URL(string: "/users/list", relativeTo: socketURL)!, method: .GET) let response = try httpClient.execute(request: req).wait() ```
1 parent a8e6f8d commit e90f5fd

File tree

3 files changed

+79
-33
lines changed

3 files changed

+79
-33
lines changed

Sources/AsyncHTTPClient/HTTPClient.swift

+18-11
Original file line numberDiff line numberDiff line change
@@ -281,15 +281,22 @@ public class HTTPClient {
281281
bootstrap = bootstrap.connectTimeout(timeout)
282282
}
283283

284-
let address = self.resolveAddress(request: request, proxy: self.configuration.proxy)
285-
bootstrap.connect(host: address.host, port: address.port)
286-
.map { channel in
287-
task.setChannel(channel)
288-
}
289-
.flatMap { channel in
290-
channel.writeAndFlush(request)
291-
}
292-
.cascadeFailure(to: task.promise)
284+
let eventLoopChannel: EventLoopFuture<Channel>
285+
if request.kind == .unixSocket, let baseURL = request.url.baseURL {
286+
eventLoopChannel = bootstrap.connect(unixDomainSocketPath: baseURL.path)
287+
} else {
288+
let address = self.resolveAddress(request: request, proxy: self.configuration.proxy)
289+
eventLoopChannel = bootstrap.connect(host: address.host, port: address.port)
290+
}
291+
292+
eventLoopChannel.map { channel in
293+
task.setChannel(channel)
294+
}
295+
.flatMap { channel in
296+
channel.writeAndFlush(request)
297+
}
298+
.cascadeFailure(to: task.promise)
299+
293300
return task
294301
}
295302

@@ -481,12 +488,12 @@ private extension ChannelPipeline {
481488
func addProxyHandler(for request: HTTPClient.Request, decoder: ByteToMessageHandler<HTTPResponseDecoder>, encoder: HTTPRequestEncoder, tlsConfiguration: TLSConfiguration?, proxy: HTTPClient.Configuration.Proxy?) -> EventLoopFuture<Void> {
482489
let handler = HTTPClientProxyHandler(host: request.host, port: request.port, authorization: proxy?.authorization, onConnect: { channel in
483490
channel.pipeline.removeHandler(decoder).flatMap {
484-
return channel.pipeline.addHandler(
491+
channel.pipeline.addHandler(
485492
ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .forwardBytes)),
486493
position: .after(encoder)
487494
)
488495
}.flatMap {
489-
return channel.pipeline.addSSLHandlerIfNeeded(for: request, tlsConfiguration: tlsConfiguration)
496+
channel.pipeline.addSSLHandlerIfNeeded(for: request, tlsConfiguration: tlsConfiguration)
490497
}
491498
})
492499
return self.addHandler(handler)

Sources/AsyncHTTPClient/HTTPHandler.swift

+51-20
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,54 @@ extension HTTPClient {
8888

8989
/// Represent HTTP request.
9090
public struct Request {
91+
/// Represent kind of Request
92+
enum Kind {
93+
/// Remote host request.
94+
case host
95+
/// UNIX Domain Socket HTTP request.
96+
case unixSocket
97+
98+
private static var hostSchemes = ["http", "https"]
99+
private static var unixSchemes = ["unix"]
100+
101+
init(forScheme scheme: String) throws {
102+
if Kind.host.supports(scheme: scheme) {
103+
self = .host
104+
} else if Kind.unixSocket.supports(scheme: scheme) {
105+
self = .unixSocket
106+
} else {
107+
throw HTTPClientError.unsupportedScheme(scheme)
108+
}
109+
}
110+
111+
func hostFromURL(_ url: URL) throws -> String {
112+
switch self {
113+
case .host:
114+
guard let host = url.host else {
115+
throw HTTPClientError.emptyHost
116+
}
117+
return host
118+
case .unixSocket:
119+
return ""
120+
}
121+
}
122+
123+
func supports(scheme: String) -> Bool {
124+
switch self {
125+
case .host:
126+
return Kind.hostSchemes.contains(scheme)
127+
case .unixSocket:
128+
return Kind.unixSchemes.contains(scheme)
129+
}
130+
}
131+
}
132+
91133
/// Request HTTP method, defaults to `GET`.
92-
public let method: HTTPMethod
134+
public var method: HTTPMethod
93135
/// Remote URL.
94-
public let url: URL
136+
public var url: URL
95137
/// Remote HTTP scheme, resolved from `URL`.
96-
public let scheme: String
138+
public var scheme: String
97139
/// Remote host, resolved from `URL`.
98140
public let host: String
99141
/// Request custom HTTP Headers, defaults to no headers.
@@ -107,6 +149,7 @@ extension HTTPClient {
107149
}
108150

109151
var redirectState: RedirectState?
152+
let kind: Kind
110153

111154
/// Create HTTP request.
112155
///
@@ -133,7 +176,6 @@ extension HTTPClient {
133176
///
134177
/// - parameters:
135178
/// - url: Remote `URL`.
136-
/// - version: HTTP version.
137179
/// - method: HTTP method.
138180
/// - headers: Custom HTTP headers.
139181
/// - body: Request body.
@@ -146,22 +188,15 @@ extension HTTPClient {
146188
throw HTTPClientError.emptyScheme
147189
}
148190

149-
guard Request.isSchemeSupported(scheme: scheme) else {
150-
throw HTTPClientError.unsupportedScheme(scheme)
151-
}
152-
153-
guard let host = url.host else {
154-
throw HTTPClientError.emptyHost
155-
}
191+
self.kind = try Kind(forScheme: scheme)
192+
self.host = try self.kind.hostFromURL(url)
156193

157-
self.method = method
194+
self.redirectState = nil
158195
self.url = url
196+
self.method = method
159197
self.scheme = scheme
160-
self.host = host
161198
self.headers = headers
162199
self.body = body
163-
164-
self.redirectState = nil
165200
}
166201

167202
/// Whether request will be executed using secure socket.
@@ -173,10 +208,6 @@ extension HTTPClient {
173208
public var port: Int {
174209
return self.url.port ?? (self.useTLS ? 443 : 80)
175210
}
176-
177-
static func isSchemeSupported(scheme: String) -> Bool {
178-
return scheme == "http" || scheme == "https"
179-
}
180211
}
181212

182213
/// Represent HTTP response.
@@ -812,7 +843,7 @@ internal struct RedirectHandler<ResponseType> {
812843
return nil
813844
}
814845

815-
guard HTTPClient.Request.isSchemeSupported(scheme: self.request.scheme) else {
846+
guard self.request.kind.supports(scheme: self.request.scheme) else {
816847
return nil
817848
}
818849

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,21 @@ class HTTPClientTests: XCTestCase {
4747

4848
let request2 = try Request(url: "https://someserver.com")
4949
XCTAssertEqual(request2.url.path, "")
50+
51+
let request3 = try Request(url: "unix:///tmp/file")
52+
XCTAssertNil(request3.url.host)
53+
XCTAssertEqual(request3.host, "")
54+
XCTAssertEqual(request3.url.path, "/tmp/file")
55+
XCTAssertEqual(request3.port, 80)
56+
XCTAssertFalse(request3.useTLS)
5057
}
5158

5259
func testBadRequestURI() throws {
5360
XCTAssertThrowsError(try Request(url: "some/path"), "should throw") { error in
5461
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyScheme)
5562
}
56-
XCTAssertThrowsError(try Request(url: "file://somewhere/some/path?foo=bar"), "should throw") { error in
57-
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.unsupportedScheme("file"))
63+
XCTAssertThrowsError(try Request(url: "app://somewhere/some/path?foo=bar"), "should throw") { error in
64+
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.unsupportedScheme("app"))
5865
}
5966
XCTAssertThrowsError(try Request(url: "https:/foo"), "should throw") { error in
6067
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyHost)
@@ -63,6 +70,7 @@ class HTTPClientTests: XCTestCase {
6370

6471
func testSchemaCasing() throws {
6572
XCTAssertNoThrow(try Request(url: "hTTpS://someserver.com:8888/some/path?foo=bar"))
73+
XCTAssertNoThrow(try Request(url: "uNIx:///some/path"))
6674
}
6775

6876
func testGet() throws {

0 commit comments

Comments
 (0)