Skip to content

Commit f314a6f

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 b65f980 commit f314a6f

File tree

6 files changed

+378
-28
lines changed

6 files changed

+378
-28
lines changed

Diff for: 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+
```

Diff for: 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
}

Diff for: Sources/AsyncHTTPClient/HTTPHandler.swift

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

525555
extension HTTPClient {

Diff for: Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift

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

Diff for: 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)