Skip to content

Commit affa03f

Browse files
Added some convenience initializers to URL and methods to Request for making requests to socket paths
Motivation: Creating URLs for connecting to servers bound to socket paths currently requires some additional code to get exactly right. It would be nice to have convenience methods on both URL and Request to assist here. Modifications: - Refactored the get/post/patch/put/delete methods so they all call into a one line execute() method. - Added variations on the above methods so they can be called with socket paths (both over HTTP and HTTPS). - Added public convenience initializers to URL to support the above, and so socket path URLs can be easily created in other situations. - Added unit tests for creating socket path URLs, and testing the new suite of convenience execute methods (that, er, test `HTTPMETHOD`s). (patch, put, and delete are now also tested as a result of these tests) - Updated the read me with basic usage instructions. Result: New methods that allow for easily creating requests to socket paths, and passing tests to go with them.
1 parent 7c8bae8 commit affa03f

File tree

6 files changed

+361
-24
lines changed

6 files changed

+361
-24
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,22 @@ httpClient.execute(request: request, delegate: delegate).futureResult.whenSucces
157157
print(count)
158158
}
159159
```
160+
161+
### Unix Domain Socket Paths
162+
Connecting to servers bound to socket paths is easy:
163+
```swift
164+
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
165+
httpClient.get(socketPath: "/tmp/myServer.socket", url: "/path/to/resource").whenComplete (...)
166+
```
167+
168+
Connecting over TLS to a unix domain socket path is possible as well:
169+
```swift
170+
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
171+
httpClient.post(secureSocketPath: "/tmp/myServer.socket", url: "/path/to/resource", body: .string("hello")).whenComplete (...)
172+
```
173+
174+
Direct URLs can easily be contructed to be executed in other scenarios:
175+
```swift
176+
let socketPathBasedURL = URL(httpURLWithSocketPath: "/tmp/myServer.socket", uri: "/path/to/resource")
177+
let secureSocketPathBasedURL = URL(httpsURLWithSocketPath: "/tmp/myServer.socket", uri: "/path/to/resource")
178+
```

Sources/AsyncHTTPClient/HTTPClient.swift

Lines changed: 159 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,27 @@ public class HTTPClient {
194194
/// - url: Remote URL.
195195
/// - deadline: Point in time by which the request must complete.
196196
public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
197-
do {
198-
let request = try Request(url: url, method: .GET)
199-
return self.execute(request: request, deadline: deadline)
200-
} catch {
201-
return self.eventLoopGroup.next().makeFailedFuture(error)
202-
}
197+
return self.execute(url: url, method: .GET, deadline: deadline)
198+
}
199+
200+
/// Execute `GET` request to a unix domain socket path, using the specified URL as the request to send to the server.
201+
///
202+
/// - parameters:
203+
/// - socketPath: The path to the unix domain socket to connect to.
204+
/// - url: The URL path and query that will be sent to the server.
205+
/// - deadline: Point in time by which the request must complete.
206+
public func get(socketPath: String, url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
207+
return self.execute(socketPath: socketPath, url: url, method: .GET, deadline: deadline)
208+
}
209+
210+
/// Execute `GET` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
211+
///
212+
/// - parameters:
213+
/// - secureSocketPath: The path to the unix domain socket to connect to.
214+
/// - url: The URL path and query that will be sent to the server.
215+
/// - deadline: Point in time by which the request must complete.
216+
public func get(secureSocketPath: String, url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
217+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .GET, deadline: deadline)
203218
}
204219

205220
/// Execute `POST` request using specified URL.
@@ -209,12 +224,29 @@ public class HTTPClient {
209224
/// - body: Request body.
210225
/// - deadline: Point in time by which the request must complete.
211226
public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
212-
do {
213-
let request = try HTTPClient.Request(url: url, method: .POST, body: body)
214-
return self.execute(request: request, deadline: deadline)
215-
} catch {
216-
return self.eventLoopGroup.next().makeFailedFuture(error)
217-
}
227+
return self.execute(url: url, method: .POST, body: body, deadline: deadline)
228+
}
229+
230+
/// Execute `POST` request to a unix domain socket path, using the specified URL as the request to send to the server.
231+
///
232+
/// - parameters:
233+
/// - socketPath: The path to the unix domain socket to connect to.
234+
/// - url: The URL path and query that will be sent to the server.
235+
/// - body: Request body.
236+
/// - deadline: Point in time by which the request must complete.
237+
public func post(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
238+
return self.execute(socketPath: socketPath, url: url, method: .POST, body: body, deadline: deadline)
239+
}
240+
241+
/// Execute `POST` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
242+
///
243+
/// - parameters:
244+
/// - secureSocketPath: The path to the unix domain socket to connect to.
245+
/// - url: The URL path and query that will be sent to the server.
246+
/// - body: Request body.
247+
/// - deadline: Point in time by which the request must complete.
248+
public func post(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
249+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .POST, body: body, deadline: deadline)
218250
}
219251

220252
/// Execute `PATCH` request using specified URL.
@@ -224,37 +256,142 @@ public class HTTPClient {
224256
/// - body: Request body.
225257
/// - deadline: Point in time by which the request must complete.
226258
public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
259+
return self.execute(url: url, method: .PATCH, body: body, deadline: deadline)
260+
}
261+
262+
/// Execute `PATCH` request to a unix domain socket path, using the specified URL as the request to send to the server.
263+
///
264+
/// - parameters:
265+
/// - socketPath: The path to the unix domain socket to connect to.
266+
/// - url: The URL path and query that will be sent to the server.
267+
/// - body: Request body.
268+
/// - deadline: Point in time by which the request must complete.
269+
public func patch(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
270+
return self.execute(socketPath: socketPath, url: url, method: .PATCH, body: body, deadline: deadline)
271+
}
272+
273+
/// Execute `PATCH` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
274+
///
275+
/// - parameters:
276+
/// - secureSocketPath: The path to the unix domain socket to connect to.
277+
/// - url: The URL path and query that will be sent to the server.
278+
/// - body: Request body.
279+
/// - deadline: Point in time by which the request must complete.
280+
public func patch(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
281+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PATCH, body: body, deadline: deadline)
282+
}
283+
284+
/// Execute `PUT` request using specified URL.
285+
///
286+
/// - parameters:
287+
/// - url: Remote URL.
288+
/// - body: Request body.
289+
/// - deadline: Point in time by which the request must complete.
290+
public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
291+
return self.execute(url: url, method: .PUT, body: body, deadline: deadline)
292+
}
293+
294+
/// Execute `PUT` request to a unix domain socket path, using the specified URL as the request to send to the server.
295+
///
296+
/// - parameters:
297+
/// - socketPath: The path to the unix domain socket to connect to.
298+
/// - url: The URL path and query that will be sent to the server.
299+
/// - body: Request body.
300+
/// - deadline: Point in time by which the request must complete.
301+
public func put(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
302+
return self.execute(socketPath: socketPath, url: url, method: .PUT, body: body, deadline: deadline)
303+
}
304+
305+
/// Execute `PUT` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
306+
///
307+
/// - parameters:
308+
/// - secureSocketPath: The path to the unix domain socket to connect to.
309+
/// - url: The URL path and query that will be sent to the server.
310+
/// - body: Request body.
311+
/// - deadline: Point in time by which the request must complete.
312+
public func put(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
313+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PUT, body: body, deadline: deadline)
314+
}
315+
316+
/// Execute `DELETE` request using specified URL.
317+
///
318+
/// - parameters:
319+
/// - url: Remote URL.
320+
/// - deadline: The time when the request must have been completed by.
321+
public func delete(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
322+
return self.execute(url: url, method: .DELETE, deadline: deadline)
323+
}
324+
325+
/// Execute `DELETE` request to a unix domain socket path, using the specified URL as the request to send to the server.
326+
///
327+
/// - parameters:
328+
/// - socketPath: The path to the unix domain socket to connect to.
329+
/// - url: The URL path and query that will be sent to the server.
330+
/// - deadline: The time when the request must have been completed by.
331+
public func delete(socketPath: String, url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
332+
return self.execute(socketPath: socketPath, url: url, method: .DELETE, deadline: deadline)
333+
}
334+
335+
/// Execute `DELETE` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
336+
///
337+
/// - parameters:
338+
/// - secureSocketPath: The path to the unix domain socket to connect to.
339+
/// - url: The URL path and query that will be sent to the server.
340+
/// - deadline: The time when the request must have been completed by.
341+
public func delete(secureSocketPath: String, url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
342+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .DELETE, deadline: deadline)
343+
}
344+
345+
/// Execute arbitrary HTTP request using specified URL.
346+
///
347+
/// - parameters:
348+
/// - url: Request url.
349+
/// - method: Request method.
350+
/// - body: Request body.
351+
/// - deadline: Point in time by which the request must complete.
352+
public func execute(url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
227353
do {
228-
let request = try HTTPClient.Request(url: url, method: .PATCH, body: body)
354+
let request = try Request(url: url, method: method, body: body)
229355
return self.execute(request: request, deadline: deadline)
230356
} catch {
231357
return self.eventLoopGroup.next().makeFailedFuture(error)
232358
}
233359
}
234360

235-
/// Execute `PUT` request using specified URL.
361+
/// Execute arbitrary HTTP+UNIX request to a unix domain socket path, using the specified URL as the request to send to the server.
236362
///
237363
/// - parameters:
238-
/// - url: Remote URL.
364+
/// - socketPath: The path to the unix domain socket to connect to.
365+
/// - url: The URL path and query that will be sent to the server.
366+
/// - method: Request method.
239367
/// - body: Request body.
240368
/// - deadline: Point in time by which the request must complete.
241-
public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
369+
public func execute(socketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
242370
do {
243-
let request = try HTTPClient.Request(url: url, method: .PUT, body: body)
371+
guard let url = URL(httpURLWithSocketPath: socketPath, uri: url) else {
372+
throw HTTPClientError.invalidURL
373+
}
374+
let request = try Request(url: url, method: method, body: body)
244375
return self.execute(request: request, deadline: deadline)
245376
} catch {
246377
return self.eventLoopGroup.next().makeFailedFuture(error)
247378
}
248379
}
249380

250-
/// Execute `DELETE` request using specified URL.
381+
/// Execute arbitrary HTTPS+UNIX request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
251382
///
252383
/// - parameters:
253-
/// - url: Remote URL.
254-
/// - deadline: The time when the request must have been completed by.
255-
public func delete(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
384+
/// - secureSocketPath: The path to the unix domain socket to connect to.
385+
/// - url: The URL path and query that will be sent to the server.
386+
/// - method: Request method.
387+
/// - body: Request body.
388+
/// - deadline: Point in time by which the request must complete.
389+
public func execute(secureSocketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
256390
do {
257-
let request = try Request(url: url, method: .DELETE)
391+
guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: url) else {
392+
throw HTTPClientError.invalidURL
393+
}
394+
let request = try Request(url: url, method: method, body: body)
258395
return self.execute(request: request, deadline: deadline)
259396
} catch {
260397
return self.eventLoopGroup.next().makeFailedFuture(error)

Sources/AsyncHTTPClient/HTTPHandler.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,36 @@ extension URL {
519519
func hasTheSameOrigin(as other: URL) -> Bool {
520520
return self.host == other.host && self.scheme == other.scheme && self.port == other.port
521521
}
522+
523+
/// Initializes a newly created HTTP URL connecting to a unix domain socket path. The socket path is encoded as the URL's host, replacing percent encoding invalid path characters, and will use the "http+unix" scheme.
524+
/// - Parameters:
525+
/// - socketPath: The path to the unix domain socket to connect to.
526+
/// - uri: The URI path and query that will be sent to the server.
527+
public init?(httpURLWithSocketPath socketPath: String, uri: String = "/") {
528+
guard let host = socketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return nil }
529+
var urlString: String
530+
if uri.hasPrefix("/") {
531+
urlString = "http+unix://\(host)\(uri)"
532+
} else {
533+
urlString = "http+unix://\(host)/\(uri)"
534+
}
535+
self.init(string: urlString)
536+
}
537+
538+
/// Initializes a newly created HTTPS URL connecting to a unix domain socket path over TLS. The socket path is encoded as the URL's host, replacing percent encoding invalid path characters, and will use the "https+unix" scheme.
539+
/// - Parameters:
540+
/// - socketPath: The path to the unix domain socket to connect to.
541+
/// - uri: The URI path and query that will be sent to the server.
542+
public init?(httpsURLWithSocketPath socketPath: String, uri: String = "/") {
543+
guard let host = socketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return nil }
544+
var urlString: String
545+
if uri.hasPrefix("/") {
546+
urlString = "https+unix://\(host)\(uri)"
547+
} else {
548+
urlString = "https+unix://\(host)/\(uri)"
549+
}
550+
self.init(string: urlString)
551+
}
522552
}
523553

524554
extension HTTPClient {

Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,11 @@ internal final class HttpBinHandler: ChannelInboundHandler {
437437
headers.add(name: "X-Calling-URI", value: req.uri)
438438
self.resps.append(HTTPResponseBuilder(status: .ok, headers: headers))
439439
return
440+
case "/echo-method":
441+
var headers = self.responseHeaders
442+
headers.add(name: "X-Method-Used", value: req.method.rawValue)
443+
self.resps.append(HTTPResponseBuilder(status: .ok, headers: headers))
444+
return
440445
case "/ok":
441446
self.resps.append(HTTPResponseBuilder(status: .ok))
442447
return

Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ extension HTTPClientTests {
2828
("testRequestURI", testRequestURI),
2929
("testBadRequestURI", testBadRequestURI),
3030
("testSchemaCasing", testSchemaCasing),
31+
("testURLSocketPathInitializers", testURLSocketPathInitializers),
32+
("testConvenienceExecuteMethods", testConvenienceExecuteMethods),
33+
("testConvenienceExecuteMethodsOverSocket", testConvenienceExecuteMethodsOverSocket),
34+
("testConvenienceExecuteMethodsOverSecureSocket", testConvenienceExecuteMethodsOverSecureSocket),
3135
("testGet", testGet),
3236
("testGetWithDifferentEventLoopBackpressure", testGetWithDifferentEventLoopBackpressure),
3337
("testPost", testPost),

0 commit comments

Comments
 (0)