Skip to content

Commit 239d076

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 a5f8553 commit 239d076

File tree

6 files changed

+378
-28
lines changed

6 files changed

+378
-28
lines changed

README.md

+19
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

+176-26
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,29 @@ public class HTTPClient {
237237
/// - deadline: Point in time by which the request must complete.
238238
/// - logger: The logger to use for this request.
239239
public func get(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture<Response> {
240-
do {
241-
let request = try Request(url: url, method: .GET)
242-
return self.execute(request: request, deadline: deadline, logger: logger)
243-
} catch {
244-
return self.eventLoopGroup.next().makeFailedFuture(error)
245-
}
240+
return self.execute(url: url, method: .GET, deadline: deadline, logger: logger)
241+
}
242+
243+
/// Execute `GET` request to a unix domain socket path, using the specified URL as the request to send to the server.
244+
///
245+
/// - parameters:
246+
/// - socketPath: The path to the unix domain socket to connect to.
247+
/// - url: The URL path and query that will be sent to the server.
248+
/// - deadline: Point in time by which the request must complete.
249+
/// - logger: The logger to use for this request.
250+
public func get(socketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
251+
return self.execute(socketPath: socketPath, url: url, method: .GET, deadline: deadline, logger: logger)
252+
}
253+
254+
/// Execute `GET` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
255+
///
256+
/// - parameters:
257+
/// - secureSocketPath: The path to the unix domain socket to connect to.
258+
/// - url: The URL path and query that will be sent to the server.
259+
/// - deadline: Point in time by which the request must complete.
260+
/// - logger: The logger to use for this request.
261+
public func get(secureSocketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
262+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .GET, deadline: deadline, logger: logger)
246263
}
247264

248265
/// Execute `POST` request using specified URL.
@@ -263,12 +280,31 @@ public class HTTPClient {
263280
/// - deadline: Point in time by which the request must complete.
264281
/// - logger: The logger to use for this request.
265282
public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture<Response> {
266-
do {
267-
let request = try HTTPClient.Request(url: url, method: .POST, body: body)
268-
return self.execute(request: request, deadline: deadline, logger: logger)
269-
} catch {
270-
return self.eventLoopGroup.next().makeFailedFuture(error)
271-
}
283+
return self.execute(url: url, method: .POST, body: body, deadline: deadline, logger: logger)
284+
}
285+
286+
/// Execute `POST` request to a unix domain socket path, using the specified URL as the request to send to the server.
287+
///
288+
/// - parameters:
289+
/// - socketPath: The path to the unix domain socket to connect to.
290+
/// - url: The URL path and query that will be sent to the server.
291+
/// - body: Request body.
292+
/// - deadline: Point in time by which the request must complete.
293+
/// - logger: The logger to use for this request.
294+
public func post(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
295+
return self.execute(socketPath: socketPath, url: url, method: .POST, body: body, deadline: deadline, logger: logger)
296+
}
297+
298+
/// Execute `POST` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
299+
///
300+
/// - parameters:
301+
/// - secureSocketPath: The path to the unix domain socket to connect to.
302+
/// - url: The URL path and query that will be sent to the server.
303+
/// - body: Request body.
304+
/// - deadline: Point in time by which the request must complete.
305+
/// - logger: The logger to use for this request.
306+
public func post(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
307+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .POST, body: body, deadline: deadline, logger: logger)
272308
}
273309

274310
/// Execute `PATCH` request using specified URL.
@@ -289,12 +325,31 @@ public class HTTPClient {
289325
/// - deadline: Point in time by which the request must complete.
290326
/// - logger: The logger to use for this request.
291327
public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture<Response> {
292-
do {
293-
let request = try HTTPClient.Request(url: url, method: .PATCH, body: body)
294-
return self.execute(request: request, deadline: deadline, logger: logger)
295-
} catch {
296-
return self.eventLoopGroup.next().makeFailedFuture(error)
297-
}
328+
return self.execute(url: url, method: .PATCH, body: body, deadline: deadline, logger: logger)
329+
}
330+
331+
/// Execute `PATCH` request to a unix domain socket path, using the specified URL as the request to send to the server.
332+
///
333+
/// - parameters:
334+
/// - socketPath: The path to the unix domain socket to connect to.
335+
/// - url: The URL path and query that will be sent to the server.
336+
/// - body: Request body.
337+
/// - deadline: Point in time by which the request must complete.
338+
/// - logger: The logger to use for this request.
339+
public func patch(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
340+
return self.execute(socketPath: socketPath, url: url, method: .PATCH, body: body, deadline: deadline, logger: logger)
341+
}
342+
343+
/// Execute `PATCH` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
344+
///
345+
/// - parameters:
346+
/// - secureSocketPath: The path to the unix domain socket to connect to.
347+
/// - url: The URL path and query that will be sent to the server.
348+
/// - body: Request body.
349+
/// - deadline: Point in time by which the request must complete.
350+
/// - logger: The logger to use for this request.
351+
public func patch(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
352+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PATCH, body: body, deadline: deadline, logger: logger)
298353
}
299354

300355
/// Execute `PUT` request using specified URL.
@@ -315,12 +370,31 @@ public class HTTPClient {
315370
/// - deadline: Point in time by which the request must complete.
316371
/// - logger: The logger to use for this request.
317372
public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture<Response> {
318-
do {
319-
let request = try HTTPClient.Request(url: url, method: .PUT, body: body)
320-
return self.execute(request: request, deadline: deadline, logger: logger)
321-
} catch {
322-
return self.eventLoopGroup.next().makeFailedFuture(error)
323-
}
373+
return self.execute(url: url, method: .PUT, body: body, deadline: deadline, logger: logger)
374+
}
375+
376+
/// Execute `PUT` request to a unix domain socket path, using the specified URL as the request to send to the server.
377+
///
378+
/// - parameters:
379+
/// - socketPath: The path to the unix domain socket to connect to.
380+
/// - url: The URL path and query that will be sent to the server.
381+
/// - body: Request body.
382+
/// - deadline: Point in time by which the request must complete.
383+
/// - logger: The logger to use for this request.
384+
public func put(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
385+
return self.execute(socketPath: socketPath, url: url, method: .PUT, body: body, deadline: deadline, logger: logger)
386+
}
387+
388+
/// Execute `PUT` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
389+
///
390+
/// - parameters:
391+
/// - secureSocketPath: The path to the unix domain socket to connect to.
392+
/// - url: The URL path and query that will be sent to the server.
393+
/// - body: Request body.
394+
/// - deadline: Point in time by which the request must complete.
395+
/// - logger: The logger to use for this request.
396+
public func put(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
397+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PUT, body: body, deadline: deadline, logger: logger)
324398
}
325399

326400
/// Execute `DELETE` request using specified URL.
@@ -339,9 +413,85 @@ public class HTTPClient {
339413
/// - deadline: The time when the request must have been completed by.
340414
/// - logger: The logger to use for this request.
341415
public func delete(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture<Response> {
416+
return self.execute(url: url, method: .DELETE, deadline: deadline, logger: logger)
417+
}
418+
419+
/// Execute `DELETE` request to a unix domain socket path, using the specified URL as the request to send to the server.
420+
///
421+
/// - parameters:
422+
/// - socketPath: The path to the unix domain socket to connect to.
423+
/// - url: The URL path and query that will be sent to the server.
424+
/// - deadline: The time when the request must have been completed by.
425+
/// - logger: The logger to use for this request.
426+
public func delete(socketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
427+
return self.execute(socketPath: socketPath, url: url, method: .DELETE, deadline: deadline, logger: logger)
428+
}
429+
430+
/// Execute `DELETE` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server.
431+
///
432+
/// - parameters:
433+
/// - secureSocketPath: The path to the unix domain socket to connect to.
434+
/// - url: The URL path and query that will be sent to the server.
435+
/// - deadline: The time when the request must have been completed by.
436+
/// - logger: The logger to use for this request.
437+
public func delete(secureSocketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
438+
return self.execute(secureSocketPath: secureSocketPath, url: url, method: .DELETE, deadline: deadline, logger: logger)
439+
}
440+
441+
/// Execute arbitrary HTTP request using specified URL.
442+
///
443+
/// - parameters:
444+
/// - url: Request url.
445+
/// - method: Request method.
446+
/// - body: Request body.
447+
/// - deadline: Point in time by which the request must complete.
448+
/// - logger: The logger to use for this request.
449+
public func execute(url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
450+
do {
451+
let request = try Request(url: url, method: method, body: body)
452+
return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled)
453+
} catch {
454+
return self.eventLoopGroup.next().makeFailedFuture(error)
455+
}
456+
}
457+
458+
/// Execute arbitrary HTTP+UNIX request to a unix domain socket path, using the specified URL as the request to send to the server.
459+
///
460+
/// - parameters:
461+
/// - socketPath: The path to the unix domain socket to connect to.
462+
/// - url: The URL path and query that will be sent to the server.
463+
/// - method: Request method.
464+
/// - body: Request body.
465+
/// - deadline: Point in time by which the request must complete.
466+
/// - logger: The logger to use for this request.
467+
public func execute(socketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
468+
do {
469+
guard let url = URL(httpURLWithSocketPath: socketPath, uri: url) else {
470+
throw HTTPClientError.invalidURL
471+
}
472+
let request = try Request(url: url, method: method, body: body)
473+
return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled)
474+
} catch {
475+
return self.eventLoopGroup.next().makeFailedFuture(error)
476+
}
477+
}
478+
479+
/// 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.
480+
///
481+
/// - parameters:
482+
/// - secureSocketPath: The path to the unix domain socket to connect to.
483+
/// - url: The URL path and query that will be sent to the server.
484+
/// - method: Request method.
485+
/// - body: Request body.
486+
/// - deadline: Point in time by which the request must complete.
487+
/// - logger: The logger to use for this request.
488+
public func execute(secureSocketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
342489
do {
343-
let request = try Request(url: url, method: .DELETE)
344-
return self.execute(request: request, deadline: deadline, logger: logger)
490+
guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: url) else {
491+
throw HTTPClientError.invalidURL
492+
}
493+
let request = try Request(url: url, method: method, body: body)
494+
return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled)
345495
} catch {
346496
return self.eventLoopGroup.next().makeFailedFuture(error)
347497
}

Sources/AsyncHTTPClient/HTTPHandler.swift

+30
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,36 @@ extension URL {
516516
func hasTheSameOrigin(as other: URL) -> Bool {
517517
return self.host == other.host && self.scheme == other.scheme && self.port == other.port
518518
}
519+
520+
/// 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.
521+
/// - Parameters:
522+
/// - socketPath: The path to the unix domain socket to connect to.
523+
/// - uri: The URI path and query that will be sent to the server.
524+
public init?(httpURLWithSocketPath socketPath: String, uri: String = "/") {
525+
guard let host = socketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return nil }
526+
var urlString: String
527+
if uri.hasPrefix("/") {
528+
urlString = "http+unix://\(host)\(uri)"
529+
} else {
530+
urlString = "http+unix://\(host)/\(uri)"
531+
}
532+
self.init(string: urlString)
533+
}
534+
535+
/// 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.
536+
/// - Parameters:
537+
/// - socketPath: The path to the unix domain socket to connect to.
538+
/// - uri: The URI path and query that will be sent to the server.
539+
public init?(httpsURLWithSocketPath socketPath: String, uri: String = "/") {
540+
guard let host = socketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return nil }
541+
var urlString: String
542+
if uri.hasPrefix("/") {
543+
urlString = "https+unix://\(host)\(uri)"
544+
} else {
545+
urlString = "https+unix://\(host)/\(uri)"
546+
}
547+
self.init(string: urlString)
548+
}
519549
}
520550

521551
extension HTTPClient {

Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift

+5
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,11 @@ internal final class HttpBinHandler: ChannelInboundHandler {
439439
headers.add(name: "X-Calling-URI", value: req.uri)
440440
self.resps.append(HTTPResponseBuilder(status: .ok, headers: headers))
441441
return
442+
case "/echo-method":
443+
var headers = self.responseHeaders
444+
headers.add(name: "X-Method-Used", value: req.method.rawValue)
445+
self.resps.append(HTTPResponseBuilder(status: .ok, headers: headers))
446+
return
442447
case "/ok":
443448
self.resps.append(HTTPResponseBuilder(status: .ok))
444449
return

Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift

+4
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)