From 81111cd9d32e82d102a1d61b57d66ff6fc63ea32 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Wed, 3 Jun 2020 20:27:34 -0700 Subject: [PATCH 1/6] Added additional tests for socketPath-based requests Motivation: While going through the existing tests, I identified a few more instances where we could add some testing. Modifications: Added one test that verifies Requests are being decoded correctly, and improved three others to check for path parsing, error throwing, and schema casing respectively. Result: Tests that continue to pass, but that will also catch any incompatible changes in the future. --- Sources/AsyncHTTPClient/HTTPHandler.swift | 4 +-- .../HTTPClientInternalTests+XCTest.swift | 1 + .../HTTPClientInternalTests.swift | 27 +++++++++++++++++++ .../HTTPClientTests.swift | 17 ++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 945f063b3..7681821ee 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -95,8 +95,8 @@ extension HTTPClient { /// Represent HTTP request. public struct Request { /// Represent kind of Request - enum Kind { - enum UnixScheme { + enum Kind: Equatable { + enum UnixScheme: Equatable { case baseURL case http_unix case https_unix diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift index 9a3e11b50..8a89eb740 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift @@ -43,6 +43,7 @@ extension HTTPClientInternalTests { ("testUploadStreamingIsCalledOnTaskEL", testUploadStreamingIsCalledOnTaskEL), ("testWeCanActuallyExactlySetTheEventLoops", testWeCanActuallyExactlySetTheEventLoops), ("testTaskPromiseBoundToEL", testTaskPromiseBoundToEL), + ("testInternalRequestURI", testInternalRequestURI), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index e5a6ad411..39d0c6f3a 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -959,4 +959,31 @@ class HTTPClientInternalTests: XCTestCase { XCTAssertTrue(task.futureResult.eventLoop === el2) XCTAssertNoThrow(try task.wait()) } + + func testInternalRequestURI() throws { + let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar") + XCTAssertEqual(request1.kind, .host) + XCTAssertEqual(request1.socketPath, "") + XCTAssertEqual(request1.uri, "/some/path?foo=bar") + + let request2 = try Request(url: "https://someserver.com") + XCTAssertEqual(request2.kind, .host) + XCTAssertEqual(request2.socketPath, "") + XCTAssertEqual(request2.uri, "/") + + let request3 = try Request(url: "unix:///tmp/file") + XCTAssertEqual(request3.kind, .unixSocket(.baseURL)) + XCTAssertEqual(request3.socketPath, "/tmp/file") + XCTAssertEqual(request3.uri, "/") + + let request4 = try Request(url: "http+unix://%2Ftmp%2Ffile/file/path") + XCTAssertEqual(request4.kind, .unixSocket(.http_unix)) + XCTAssertEqual(request4.socketPath, "/tmp/file") + XCTAssertEqual(request4.uri, "/file/path") + + let request5 = try Request(url: "https+unix://%2Ftmp%2Ffile/file/path") + XCTAssertEqual(request5.kind, .unixSocket(.https_unix)) + XCTAssertEqual(request5.socketPath, "/tmp/file") + XCTAssertEqual(request5.uri, "/file/path") + } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 511b3b1c6..68205daeb 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -98,6 +98,18 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(request3.url.path, "/tmp/file") XCTAssertEqual(request3.port, 80) XCTAssertFalse(request3.useTLS) + + let request4 = try Request(url: "http+unix://%2Ftmp%2Ffile/file/path") + XCTAssertEqual(request4.host, "") + XCTAssertEqual(request4.url.host, "/tmp/file") + XCTAssertEqual(request4.url.path, "/file/path") + XCTAssertFalse(request4.useTLS) + + let request5 = try Request(url: "https+unix://%2Ftmp%2Ffile/file/path") + XCTAssertEqual(request5.host, "") + XCTAssertEqual(request5.url.host, "/tmp/file") + XCTAssertEqual(request5.url.path, "/file/path") + XCTAssertTrue(request5.useTLS) } func testBadRequestURI() throws { @@ -110,11 +122,16 @@ class HTTPClientTests: XCTestCase { XCTAssertThrowsError(try Request(url: "https:/foo"), "should throw") { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyHost) } + XCTAssertThrowsError(try Request(url: "http+unix:///path"), "should throw") { error in + XCTAssertEqual(error as! HTTPClientError, HTTPClientError.missingSocketPath) + } } func testSchemaCasing() throws { XCTAssertNoThrow(try Request(url: "hTTpS://someserver.com:8888/some/path?foo=bar")) XCTAssertNoThrow(try Request(url: "uNIx:///some/path")) + XCTAssertNoThrow(try Request(url: "hTtP+uNIx://%2Fsome%2Fpath/")) + XCTAssertNoThrow(try Request(url: "hTtPS+uNIx://%2Fsome%2Fpath/")) } func testGet() throws { From a5f85534569c0d8acae34a947b6deda5193c3082 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Tue, 9 Jun 2020 14:58:45 -0700 Subject: [PATCH 2/6] Fixed some minor issues introduces with logging Motivation: Some of the convenience request methods weren't properly adapted for logging. Modifications: - Removed a doc comment from patch() that incorrectly referenced a logger. - Fixed an issue where patch() would call into post(). - Added a doc comment to delete() that references the logger. - Tests for the above come in the next commit... Result: Correct documentation and functionality for the patch() and delete() methods. --- Sources/AsyncHTTPClient/HTTPClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index faa31d06d..ae4618731 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -277,9 +277,8 @@ public class HTTPClient { /// - url: Remote URL. /// - body: Request body. /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.post(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) + return self.patch(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute `PATCH` request using specified URL. @@ -338,6 +337,7 @@ public class HTTPClient { /// - parameters: /// - url: Remote URL. /// - deadline: The time when the request must have been completed by. + /// - logger: The logger to use for this request. public func delete(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { do { let request = try Request(url: url, method: .DELETE) From 239d076c4b670b6e0cfb57d0de3dc8b5c468fc52 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Wed, 3 Jun 2020 20:42:25 -0700 Subject: [PATCH 3/6] 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. --- README.md | 19 ++ Sources/AsyncHTTPClient/HTTPClient.swift | 202 +++++++++++++++--- Sources/AsyncHTTPClient/HTTPHandler.swift | 30 +++ .../HTTPClientTestUtils.swift | 5 + .../HTTPClientTests+XCTest.swift | 4 + .../HTTPClientTests.swift | 146 ++++++++++++- 6 files changed, 378 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index a47e2572a..1675d8a87 100644 --- a/README.md +++ b/README.md @@ -157,3 +157,22 @@ httpClient.execute(request: request, delegate: delegate).futureResult.whenSucces print(count) } ``` + +### Unix Domain Socket Paths +Connecting to servers bound to socket paths is easy: +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +httpClient.get(socketPath: "/tmp/myServer.socket", url: "/path/to/resource").whenComplete (...) +``` + +Connecting over TLS to a unix domain socket path is possible as well: +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +httpClient.post(secureSocketPath: "/tmp/myServer.socket", url: "/path/to/resource", body: .string("hello")).whenComplete (...) +``` + +Direct URLs can easily be contructed to be executed in other scenarios: +```swift +let socketPathBasedURL = URL(httpURLWithSocketPath: "/tmp/myServer.socket", uri: "/path/to/resource") +let secureSocketPathBasedURL = URL(httpsURLWithSocketPath: "/tmp/myServer.socket", uri: "/path/to/resource") +``` diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index ae4618731..8cdc33ea4 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -237,12 +237,29 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func get(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - do { - let request = try Request(url: url, method: .GET) - return self.execute(request: request, deadline: deadline, logger: logger) - } catch { - return self.eventLoopGroup.next().makeFailedFuture(error) - } + return self.execute(url: url, method: .GET, deadline: deadline, logger: logger) + } + + /// Execute `GET` request to a unix domain socket path, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func get(socketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(socketPath: socketPath, url: url, method: .GET, deadline: deadline, logger: logger) + } + + /// Execute `GET` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func get(secureSocketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(secureSocketPath: secureSocketPath, url: url, method: .GET, deadline: deadline, logger: logger) } /// Execute `POST` request using specified URL. @@ -263,12 +280,31 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - do { - let request = try HTTPClient.Request(url: url, method: .POST, body: body) - return self.execute(request: request, deadline: deadline, logger: logger) - } catch { - return self.eventLoopGroup.next().makeFailedFuture(error) - } + return self.execute(url: url, method: .POST, body: body, deadline: deadline, logger: logger) + } + + /// Execute `POST` request to a unix domain socket path, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func post(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(socketPath: socketPath, url: url, method: .POST, body: body, deadline: deadline, logger: logger) + } + + /// Execute `POST` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func post(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(secureSocketPath: secureSocketPath, url: url, method: .POST, body: body, deadline: deadline, logger: logger) } /// Execute `PATCH` request using specified URL. @@ -289,12 +325,31 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - do { - let request = try HTTPClient.Request(url: url, method: .PATCH, body: body) - return self.execute(request: request, deadline: deadline, logger: logger) - } catch { - return self.eventLoopGroup.next().makeFailedFuture(error) - } + return self.execute(url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) + } + + /// Execute `PATCH` request to a unix domain socket path, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func patch(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(socketPath: socketPath, url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) + } + + /// Execute `PATCH` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func patch(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) } /// Execute `PUT` request using specified URL. @@ -315,12 +370,31 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - do { - let request = try HTTPClient.Request(url: url, method: .PUT, body: body) - return self.execute(request: request, deadline: deadline, logger: logger) - } catch { - return self.eventLoopGroup.next().makeFailedFuture(error) - } + return self.execute(url: url, method: .PUT, body: body, deadline: deadline, logger: logger) + } + + /// Execute `PUT` request to a unix domain socket path, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func put(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(socketPath: socketPath, url: url, method: .PUT, body: body, deadline: deadline, logger: logger) + } + + /// Execute `PUT` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func put(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PUT, body: body, deadline: deadline, logger: logger) } /// Execute `DELETE` request using specified URL. @@ -339,9 +413,85 @@ public class HTTPClient { /// - deadline: The time when the request must have been completed by. /// - logger: The logger to use for this request. public func delete(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { + return self.execute(url: url, method: .DELETE, deadline: deadline, logger: logger) + } + + /// Execute `DELETE` request to a unix domain socket path, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - deadline: The time when the request must have been completed by. + /// - logger: The logger to use for this request. + public func delete(socketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(socketPath: socketPath, url: url, method: .DELETE, deadline: deadline, logger: logger) + } + + /// Execute `DELETE` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - deadline: The time when the request must have been completed by. + /// - logger: The logger to use for this request. + public func delete(secureSocketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + return self.execute(secureSocketPath: secureSocketPath, url: url, method: .DELETE, deadline: deadline, logger: logger) + } + + /// Execute arbitrary HTTP request using specified URL. + /// + /// - parameters: + /// - url: Request url. + /// - method: Request method. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func execute(url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + do { + let request = try Request(url: url, method: method, body: body) + return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled) + } catch { + return self.eventLoopGroup.next().makeFailedFuture(error) + } + } + + /// Execute arbitrary HTTP+UNIX request to a unix domain socket path, using the specified URL as the request to send to the server. + /// + /// - parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - method: Request method. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func execute(socketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + do { + guard let url = URL(httpURLWithSocketPath: socketPath, uri: url) else { + throw HTTPClientError.invalidURL + } + let request = try Request(url: url, method: method, body: body) + return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled) + } catch { + return self.eventLoopGroup.next().makeFailedFuture(error) + } + } + + /// 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. + /// + /// - parameters: + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - url: The URL path and query that will be sent to the server. + /// - method: Request method. + /// - body: Request body. + /// - deadline: Point in time by which the request must complete. + /// - logger: The logger to use for this request. + public func execute(secureSocketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { do { - let request = try Request(url: url, method: .DELETE) - return self.execute(request: request, deadline: deadline, logger: logger) + guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: url) else { + throw HTTPClientError.invalidURL + } + let request = try Request(url: url, method: method, body: body) + return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled) } catch { return self.eventLoopGroup.next().makeFailedFuture(error) } diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 7681821ee..fb32f482c 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -516,6 +516,36 @@ extension URL { func hasTheSameOrigin(as other: URL) -> Bool { return self.host == other.host && self.scheme == other.scheme && self.port == other.port } + + /// 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. + /// - Parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - uri: The URI path and query that will be sent to the server. + public init?(httpURLWithSocketPath socketPath: String, uri: String = "/") { + guard let host = socketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return nil } + var urlString: String + if uri.hasPrefix("/") { + urlString = "http+unix://\(host)\(uri)" + } else { + urlString = "http+unix://\(host)/\(uri)" + } + self.init(string: urlString) + } + + /// 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. + /// - Parameters: + /// - socketPath: The path to the unix domain socket to connect to. + /// - uri: The URI path and query that will be sent to the server. + public init?(httpsURLWithSocketPath socketPath: String, uri: String = "/") { + guard let host = socketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return nil } + var urlString: String + if uri.hasPrefix("/") { + urlString = "https+unix://\(host)\(uri)" + } else { + urlString = "https+unix://\(host)/\(uri)" + } + self.init(string: urlString) + } } extension HTTPClient { diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index ed65fd7b0..e5df9cfc5 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -439,6 +439,11 @@ internal final class HttpBinHandler: ChannelInboundHandler { headers.add(name: "X-Calling-URI", value: req.uri) self.resps.append(HTTPResponseBuilder(status: .ok, headers: headers)) return + case "/echo-method": + var headers = self.responseHeaders + headers.add(name: "X-Method-Used", value: req.method.rawValue) + self.resps.append(HTTPResponseBuilder(status: .ok, headers: headers)) + return case "/ok": self.resps.append(HTTPResponseBuilder(status: .ok)) return diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 5d8ba873f..83efb6f0d 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -28,6 +28,10 @@ extension HTTPClientTests { ("testRequestURI", testRequestURI), ("testBadRequestURI", testBadRequestURI), ("testSchemaCasing", testSchemaCasing), + ("testURLSocketPathInitializers", testURLSocketPathInitializers), + ("testConvenienceExecuteMethods", testConvenienceExecuteMethods), + ("testConvenienceExecuteMethodsOverSocket", testConvenienceExecuteMethodsOverSocket), + ("testConvenienceExecuteMethodsOverSecureSocket", testConvenienceExecuteMethodsOverSecureSocket), ("testGet", testGet), ("testGetWithDifferentEventLoopBackpressure", testGetWithDifferentEventLoopBackpressure), ("testPost", testPost), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 68205daeb..fc25fdec3 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -134,6 +134,148 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try Request(url: "hTtPS+uNIx://%2Fsome%2Fpath/")) } + func testURLSocketPathInitializers() throws { + let url1 = URL(httpURLWithSocketPath: "/tmp/file") + XCTAssertNotNil(url1) + if let url = url1 { + XCTAssertEqual(url.scheme, "http+unix") + XCTAssertEqual(url.host, "/tmp/file") + XCTAssertEqual(url.path, "/") + XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/") + } + + let url2 = URL(httpURLWithSocketPath: "/tmp/file", uri: "/file/path") + XCTAssertNotNil(url2) + if let url = url2 { + XCTAssertEqual(url.scheme, "http+unix") + XCTAssertEqual(url.host, "/tmp/file") + XCTAssertEqual(url.path, "/file/path") + XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/file/path") + } + + let url3 = URL(httpURLWithSocketPath: "/tmp/file", uri: "file/path") + XCTAssertNotNil(url3) + if let url = url3 { + XCTAssertEqual(url.scheme, "http+unix") + XCTAssertEqual(url.host, "/tmp/file") + XCTAssertEqual(url.path, "/file/path") + XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/file/path") + } + + let url4 = URL(httpURLWithSocketPath: "/tmp/file with spacesと漢字", uri: "file/path") + XCTAssertNotNil(url4) + if let url = url4 { + XCTAssertEqual(url.scheme, "http+unix") + XCTAssertEqual(url.host, "/tmp/file with spacesと漢字") + XCTAssertEqual(url.path, "/file/path") + XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") + } + + let url5 = URL(httpsURLWithSocketPath: "/tmp/file") + XCTAssertNotNil(url5) + if let url = url5 { + XCTAssertEqual(url.scheme, "https+unix") + XCTAssertEqual(url.host, "/tmp/file") + XCTAssertEqual(url.path, "/") + XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/") + } + + let url6 = URL(httpsURLWithSocketPath: "/tmp/file", uri: "/file/path") + XCTAssertNotNil(url6) + if let url = url6 { + XCTAssertEqual(url.scheme, "https+unix") + XCTAssertEqual(url.host, "/tmp/file") + XCTAssertEqual(url.path, "/file/path") + XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/file/path") + } + + let url7 = URL(httpsURLWithSocketPath: "/tmp/file", uri: "file/path") + XCTAssertNotNil(url7) + if let url = url7 { + XCTAssertEqual(url.scheme, "https+unix") + XCTAssertEqual(url.host, "/tmp/file") + XCTAssertEqual(url.path, "/file/path") + XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/file/path") + } + + let url8 = URL(httpsURLWithSocketPath: "/tmp/file with spacesと漢字", uri: "file/path") + XCTAssertNotNil(url8) + if let url = url8 { + XCTAssertEqual(url.scheme, "https+unix") + XCTAssertEqual(url.host, "/tmp/file with spacesと漢字") + XCTAssertEqual(url.path, "/file/path") + XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") + } + + let url9 = URL(httpURLWithSocketPath: "/tmp/file", uri: " ") + XCTAssertNil(url9) + + let url10 = URL(httpsURLWithSocketPath: "/tmp/file", uri: " ") + XCTAssertNil(url10) + } + + func testConvenienceExecuteMethods() throws { + XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], + try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], + try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["PATCH"[...]], + try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["PUT"[...]], + try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["DELETE"[...]], + try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["CHECKOUT"[...]], + try self.defaultClient.execute(url: self.defaultHTTPBinURLPrefix + "echo-method", method: .CHECKOUT).wait().headers[canonicalForm: "X-Method-Used"])) + } + + func testConvenienceExecuteMethodsOverSocket() throws { + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + defer { + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } + + XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], + try self.defaultClient.get(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], + try self.defaultClient.post(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["PATCH"[...]], + try self.defaultClient.patch(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["PUT"[...]], + try self.defaultClient.put(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["DELETE"[...]], + try self.defaultClient.delete(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["CHECKOUT"[...]], + try self.defaultClient.execute(socketPath: path, url: "echo-method", method: .CHECKOUT).wait().headers[canonicalForm: "X-Method-Used"])) + }) + } + + func testConvenienceExecuteMethodsOverSecureSocket() throws { + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localSocketPathHTTPBin = HTTPBin(ssl: true, bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none)) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } + + XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], + try localClient.get(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], + try localClient.post(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["PATCH"[...]], + try localClient.patch(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["PUT"[...]], + try localClient.put(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["DELETE"[...]], + try localClient.delete(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["CHECKOUT"[...]], + try localClient.execute(secureSocketPath: path, url: "echo-method", method: .CHECKOUT).wait().headers[canonicalForm: "X-Method-Used"])) + }) + } + func testGet() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() XCTAssertEqual(.ok, response.status) @@ -1368,7 +1510,7 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try localHTTPBin.shutdown()) } - guard let target = URL(string: "http+unix://\(path.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/echo-uri"), + guard let target = URL(httpURLWithSocketPath: path, uri: "/echo-uri"), let request = try? Request(url: target) else { XCTFail("couldn't build URL for request") return @@ -1388,7 +1530,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - guard let target = URL(string: "https+unix://\(path.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/echo-uri"), + guard let target = URL(httpsURLWithSocketPath: path, uri: "/echo-uri"), let request = try? Request(url: target) else { XCTFail("couldn't build URL for request") return From 89284adaa6ab1c937a1c343cd622340fc2c3247c Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Tue, 9 Jun 2020 13:24:30 -0700 Subject: [PATCH 4/6] Removed some of the new public methods added for creating a socket-path based request Motivation: I previously added too much new public API that will most likely not be necessary, and can be better accessed using a generic execute method. Modifications: Removed the get/post/patch/put/delete methods that were specific to socket paths. Result: Less new public API. --- README.md | 4 +- Sources/AsyncHTTPClient/HTTPClient.swift | 116 ------------------ .../HTTPClientTests.swift | 24 +--- 3 files changed, 6 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 1675d8a87..f2812be84 100644 --- a/README.md +++ b/README.md @@ -162,13 +162,13 @@ httpClient.execute(request: request, delegate: delegate).futureResult.whenSucces Connecting to servers bound to socket paths is easy: ```swift let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) -httpClient.get(socketPath: "/tmp/myServer.socket", url: "/path/to/resource").whenComplete (...) +httpClient.execute(socketPath: "/tmp/myServer.socket", url: "/path/to/resource", method: .GET).whenComplete (...) ``` Connecting over TLS to a unix domain socket path is possible as well: ```swift let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) -httpClient.post(secureSocketPath: "/tmp/myServer.socket", url: "/path/to/resource", body: .string("hello")).whenComplete (...) +httpClient.execute(secureSocketPath: "/tmp/myServer.socket", url: "/path/to/resource", body: .string("hello"), method: .POST).whenComplete (...) ``` Direct URLs can easily be contructed to be executed in other scenarios: diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 8cdc33ea4..74d0fd8ea 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -240,28 +240,6 @@ public class HTTPClient { return self.execute(url: url, method: .GET, deadline: deadline, logger: logger) } - /// Execute `GET` request to a unix domain socket path, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - socketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func get(socketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(socketPath: socketPath, url: url, method: .GET, deadline: deadline, logger: logger) - } - - /// Execute `GET` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - secureSocketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func get(secureSocketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(secureSocketPath: secureSocketPath, url: url, method: .GET, deadline: deadline, logger: logger) - } - /// Execute `POST` request using specified URL. /// /// - parameters: @@ -283,30 +261,6 @@ public class HTTPClient { return self.execute(url: url, method: .POST, body: body, deadline: deadline, logger: logger) } - /// Execute `POST` request to a unix domain socket path, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - socketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - body: Request body. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func post(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(socketPath: socketPath, url: url, method: .POST, body: body, deadline: deadline, logger: logger) - } - - /// Execute `POST` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - secureSocketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - body: Request body. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func post(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(secureSocketPath: secureSocketPath, url: url, method: .POST, body: body, deadline: deadline, logger: logger) - } - /// Execute `PATCH` request using specified URL. /// /// - parameters: @@ -328,30 +282,6 @@ public class HTTPClient { return self.execute(url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) } - /// Execute `PATCH` request to a unix domain socket path, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - socketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - body: Request body. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func patch(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(socketPath: socketPath, url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) - } - - /// Execute `PATCH` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - secureSocketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - body: Request body. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func patch(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) - } - /// Execute `PUT` request using specified URL. /// /// - parameters: @@ -373,30 +303,6 @@ public class HTTPClient { return self.execute(url: url, method: .PUT, body: body, deadline: deadline, logger: logger) } - /// Execute `PUT` request to a unix domain socket path, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - socketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - body: Request body. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func put(socketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(socketPath: socketPath, url: url, method: .PUT, body: body, deadline: deadline, logger: logger) - } - - /// Execute `PUT` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - secureSocketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - body: Request body. - /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func put(secureSocketPath: String, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(secureSocketPath: secureSocketPath, url: url, method: .PUT, body: body, deadline: deadline, logger: logger) - } - /// Execute `DELETE` request using specified URL. /// /// - parameters: @@ -416,28 +322,6 @@ public class HTTPClient { return self.execute(url: url, method: .DELETE, deadline: deadline, logger: logger) } - /// Execute `DELETE` request to a unix domain socket path, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - socketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - deadline: The time when the request must have been completed by. - /// - logger: The logger to use for this request. - public func delete(socketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(socketPath: socketPath, url: url, method: .DELETE, deadline: deadline, logger: logger) - } - - /// Execute `DELETE` request to a unix domain socket path over TLS, using the specified URL as the request to send to the server. - /// - /// - parameters: - /// - secureSocketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. - /// - deadline: The time when the request must have been completed by. - /// - logger: The logger to use for this request. - public func delete(secureSocketPath: String, url: String, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { - return self.execute(secureSocketPath: secureSocketPath, url: url, method: .DELETE, deadline: deadline, logger: logger) - } - /// Execute arbitrary HTTP request using specified URL. /// /// - parameters: diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index fc25fdec3..f70828aef 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -237,17 +237,9 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], - try self.defaultClient.get(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + try self.defaultClient.execute(socketPath: path, url: "echo-method", method: .GET).wait().headers[canonicalForm: "X-Method-Used"])) XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], - try self.defaultClient.post(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["PATCH"[...]], - try self.defaultClient.patch(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["PUT"[...]], - try self.defaultClient.put(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["DELETE"[...]], - try self.defaultClient.delete(socketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["CHECKOUT"[...]], - try self.defaultClient.execute(socketPath: path, url: "echo-method", method: .CHECKOUT).wait().headers[canonicalForm: "X-Method-Used"])) + try self.defaultClient.execute(socketPath: path, url: "echo-method", method: .POST).wait().headers[canonicalForm: "X-Method-Used"])) }) } @@ -262,17 +254,9 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], - try localClient.get(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + try localClient.execute(secureSocketPath: path, url: "echo-method", method: .GET).wait().headers[canonicalForm: "X-Method-Used"])) XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], - try localClient.post(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["PATCH"[...]], - try localClient.patch(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["PUT"[...]], - try localClient.put(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["DELETE"[...]], - try localClient.delete(secureSocketPath: path, url: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) - XCTAssertNoThrow(XCTAssertEqual(["CHECKOUT"[...]], - try localClient.execute(secureSocketPath: path, url: "echo-method", method: .CHECKOUT).wait().headers[canonicalForm: "X-Method-Used"])) + try localClient.execute(secureSocketPath: path, url: "echo-method", method: .POST).wait().headers[canonicalForm: "X-Method-Used"])) }) } From 0b55712cd0f748844ec927003efd7b47c1e23998 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Tue, 9 Jun 2020 13:45:25 -0700 Subject: [PATCH 5/6] Renamed execute(url:) methods such that the HTTP method is the first argument in the parameter list Motivation: If these are intended to be general methods for building simple requests, then it makes sense to have the method be the first parameter in the list. Modifications: Moved the `method: HTTPMethod` parameter to the front of the list for all `execute([...] url: [...])` methods, and made it default to .GET. I also changed the url parameter to be `urlPath` for the two socketPath based execute methods. Result: A cleaner public interface for users of the API. --- README.md | 4 +-- Sources/AsyncHTTPClient/HTTPClient.swift | 30 +++++++++---------- .../HTTPClientTests.swift | 16 ++++++---- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f2812be84..0474f6b17 100644 --- a/README.md +++ b/README.md @@ -162,13 +162,13 @@ httpClient.execute(request: request, delegate: delegate).futureResult.whenSucces Connecting to servers bound to socket paths is easy: ```swift let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) -httpClient.execute(socketPath: "/tmp/myServer.socket", url: "/path/to/resource", method: .GET).whenComplete (...) +httpClient.execute(.GET, socketPath: "/tmp/myServer.socket", urlPath: "/path/to/resource").whenComplete (...) ``` Connecting over TLS to a unix domain socket path is possible as well: ```swift let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) -httpClient.execute(secureSocketPath: "/tmp/myServer.socket", url: "/path/to/resource", body: .string("hello"), method: .POST).whenComplete (...) +httpClient.execute(.POST, secureSocketPath: "/tmp/myServer.socket", urlPath: "/path/to/resource", body: .string("hello")).whenComplete (...) ``` Direct URLs can easily be contructed to be executed in other scenarios: diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 74d0fd8ea..1910157dc 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -237,7 +237,7 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func get(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(url: url, method: .GET, deadline: deadline, logger: logger) + return self.execute(.GET, url: url, deadline: deadline, logger: logger) } /// Execute `POST` request using specified URL. @@ -258,7 +258,7 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(url: url, method: .POST, body: body, deadline: deadline, logger: logger) + return self.execute(.POST, url: url, body: body, deadline: deadline, logger: logger) } /// Execute `PATCH` request using specified URL. @@ -279,7 +279,7 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(url: url, method: .PATCH, body: body, deadline: deadline, logger: logger) + return self.execute(.PATCH, url: url, body: body, deadline: deadline, logger: logger) } /// Execute `PUT` request using specified URL. @@ -300,7 +300,7 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(url: url, method: .PUT, body: body, deadline: deadline, logger: logger) + return self.execute(.PUT, url: url, body: body, deadline: deadline, logger: logger) } /// Execute `DELETE` request using specified URL. @@ -319,18 +319,18 @@ public class HTTPClient { /// - deadline: The time when the request must have been completed by. /// - logger: The logger to use for this request. public func delete(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(url: url, method: .DELETE, deadline: deadline, logger: logger) + return self.execute(.DELETE, url: url, deadline: deadline, logger: logger) } /// Execute arbitrary HTTP request using specified URL. /// /// - parameters: - /// - url: Request url. /// - method: Request method. + /// - url: Request url. /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + public func execute(_ method: HTTPMethod = .GET, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { do { let request = try Request(url: url, method: method, body: body) return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled) @@ -342,15 +342,15 @@ public class HTTPClient { /// Execute arbitrary HTTP+UNIX request to a unix domain socket path, using the specified URL as the request to send to the server. /// /// - parameters: - /// - socketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. /// - method: Request method. + /// - socketPath: The path to the unix domain socket to connect to. + /// - urlPath: The URL path and query that will be sent to the server. /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(socketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + public func execute(_ method: HTTPMethod = .GET, socketPath: String, urlPath: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { do { - guard let url = URL(httpURLWithSocketPath: socketPath, uri: url) else { + guard let url = URL(httpURLWithSocketPath: socketPath, uri: urlPath) else { throw HTTPClientError.invalidURL } let request = try Request(url: url, method: method, body: body) @@ -363,15 +363,15 @@ public class HTTPClient { /// 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. /// /// - parameters: - /// - secureSocketPath: The path to the unix domain socket to connect to. - /// - url: The URL path and query that will be sent to the server. /// - method: Request method. + /// - secureSocketPath: The path to the unix domain socket to connect to. + /// - urlPath: The URL path and query that will be sent to the server. /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(secureSocketPath: String, url: String, method: HTTPMethod, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + public func execute(_ method: HTTPMethod = .GET, secureSocketPath: String, urlPath: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { do { - guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: url) else { + guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: urlPath) else { throw HTTPClientError.invalidURL } let request = try Request(url: url, method: method, body: body) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index f70828aef..ee82085f8 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -225,8 +225,10 @@ class HTTPClientTests: XCTestCase { try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) XCTAssertNoThrow(XCTAssertEqual(["DELETE"[...]], try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], + try self.defaultClient.execute(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) XCTAssertNoThrow(XCTAssertEqual(["CHECKOUT"[...]], - try self.defaultClient.execute(url: self.defaultHTTPBinURLPrefix + "echo-method", method: .CHECKOUT).wait().headers[canonicalForm: "X-Method-Used"])) + try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) } func testConvenienceExecuteMethodsOverSocket() throws { @@ -237,9 +239,11 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], - try self.defaultClient.execute(socketPath: path, url: "echo-method", method: .GET).wait().headers[canonicalForm: "X-Method-Used"])) + try self.defaultClient.execute(socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], + try self.defaultClient.execute(.GET, socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], - try self.defaultClient.execute(socketPath: path, url: "echo-method", method: .POST).wait().headers[canonicalForm: "X-Method-Used"])) + try self.defaultClient.execute(.POST, socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) }) } @@ -254,9 +258,11 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], - try localClient.execute(secureSocketPath: path, url: "echo-method", method: .GET).wait().headers[canonicalForm: "X-Method-Used"])) + try localClient.execute(secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) + XCTAssertNoThrow(XCTAssertEqual(["GET"[...]], + try localClient.execute(.GET, secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) XCTAssertNoThrow(XCTAssertEqual(["POST"[...]], - try localClient.execute(secureSocketPath: path, url: "echo-method", method: .POST).wait().headers[canonicalForm: "X-Method-Used"])) + try localClient.execute(.POST, secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"])) }) } From d8f9c1b81498bce88502c622202ef5397b3bda27 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Wed, 10 Jun 2020 12:27:43 -0700 Subject: [PATCH 6/6] Updated logging tests to also check the new execute methods Motivation: The logging tests previously didn't check for socket path-based requests. Modifications: Updated the `testAllMethodsLog()` and `testAllMethodsLog()` tests to include checks for each of the new `execute()` methods. Result: Two more tests that pass. --- .../HTTPClientTests.swift | 126 +++++++++++++++++- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index ee82085f8..ec8eb92fe 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -2091,7 +2091,70 @@ class HTTPClientTests: XCTestCase { logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) + // === Synthesized Request + XCTAssertNoThrow(try self.defaultClient.execute(.GET, + url: self.defaultHTTPBinURLPrefix + "get", + body: nil, + deadline: nil, + logger: logger).wait()) + XCTAssertEqual(0, logStore.allEntries.count) + XCTAssertEqual(0, self.backgroundLogStore.allEntries.count) + + // === Synthesized Socket Path Request + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger(label: "\(#function)", factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + }) + backgroundLogger.logLevel = .trace + + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + backgroundActivityLogger: backgroundLogger) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } + + XCTAssertNoThrow(try localClient.execute(.GET, + socketPath: path, + urlPath: "get", + body: nil, + deadline: nil, + logger: logger).wait()) + XCTAssertEqual(0, logStore.allEntries.count) + + XCTAssertEqual(0, backgroundLogStore.allEntries.count) + }) + + // === Synthesized Secure Socket Path Request + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger(label: "\(#function)", factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + }) + backgroundLogger.logLevel = .trace + + let localSocketPathHTTPBin = HTTPBin(ssl: true, bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none), + backgroundActivityLogger: backgroundLogger) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } + + XCTAssertNoThrow(try localClient.execute(.GET, + secureSocketPath: path, + urlPath: "get", + body: nil, + deadline: nil, + logger: logger).wait()) + XCTAssertEqual(0, logStore.allEntries.count) + + XCTAssertEqual(0, backgroundLogStore.allEntries.count) + }) } func testAllMethodsLog() { @@ -2104,7 +2167,7 @@ class HTTPClientTests: XCTestCase { logger.logLevel = .trace logger[metadataKey: "req"] = "yo-\(type)" - let url = self.defaultHTTPBinURLPrefix + "not-found/request/\(type))" + let url = "not-found/request/\(type))" let result = try body(logger, url) XCTAssertGreaterThan(logStore.allEntries.count, 0) @@ -2116,27 +2179,78 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in - try self.defaultClient.get(url: url, logger: logger).wait() + try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status)) XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PUT") { logger, url in - try self.defaultClient.put(url: url, logger: logger).wait() + try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status)) XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "POST") { logger, url in - try self.defaultClient.post(url: url, logger: logger).wait() + try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status)) XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "DELETE") { logger, url in - try self.defaultClient.delete(url: url, logger: logger).wait() + try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status)) XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PATCH") { logger, url in - try self.defaultClient.patch(url: url, logger: logger).wait() + try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() + }.status)) + + XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "CHECKOUT") { logger, url in + try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status)) // No background activity expected here. XCTAssertEqual(0, self.backgroundLogStore.allEntries.count) + + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger(label: "\(#function)", factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + }) + backgroundLogger.logLevel = .trace + + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + backgroundActivityLogger: backgroundLogger) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } + + XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in + try localClient.execute(socketPath: path, urlPath: url, logger: logger).wait() + }.status)) + + // No background activity expected here. + XCTAssertEqual(0, backgroundLogStore.allEntries.count) + }) + + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger(label: "\(#function)", factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + }) + backgroundLogger.logLevel = .trace + + let localSocketPathHTTPBin = HTTPBin(ssl: true, bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none), + backgroundActivityLogger: backgroundLogger) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } + + XCTAssertNoThrow(XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in + try localClient.execute(secureSocketPath: path, urlPath: url, logger: logger).wait() + }.status)) + + // No background activity expected here. + XCTAssertEqual(0, backgroundLogStore.allEntries.count) + }) } func testClosingIdleConnectionsInPoolLogsInTheBackground() {