Skip to content

Convenience methods for socket paths #235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.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(.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:
```swift
let socketPathBasedURL = URL(httpURLWithSocketPath: "/tmp/myServer.socket", uri: "/path/to/resource")
let secureSocketPathBasedURL = URL(httpsURLWithSocketPath: "/tmp/myServer.socket", uri: "/path/to/resource")
```
90 changes: 62 additions & 28 deletions Sources/AsyncHTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +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<Response> {
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(.GET, url: url, deadline: deadline, logger: logger)
}

/// Execute `POST` request using specified URL.
Expand All @@ -263,12 +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<Response> {
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(.POST, url: url, body: body, deadline: deadline, logger: logger)
}

/// Execute `PATCH` request using specified URL.
Expand All @@ -277,9 +267,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<Response> {
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.
Expand All @@ -290,12 +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<Response> {
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(.PATCH, url: url, body: body, deadline: deadline, logger: logger)
}

/// Execute `PUT` request using specified URL.
Expand All @@ -316,12 +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<Response> {
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(.PUT, url: url, body: body, deadline: deadline, logger: logger)
}

/// Execute `DELETE` request using specified URL.
Expand All @@ -338,10 +317,65 @@ 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<Response> {
return self.execute(.DELETE, url: url, deadline: deadline, logger: logger)
}

/// Execute arbitrary HTTP request using specified URL.
///
/// - parameters:
/// - 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(_ method: HTTPMethod = .GET, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
do {
let request = try Request(url: url, method: .DELETE)
return self.execute(request: request, deadline: deadline, logger: logger)
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:
/// - 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(_ method: HTTPMethod = .GET, socketPath: String, urlPath: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
do {
guard let url = URL(httpURLWithSocketPath: socketPath, uri: urlPath) 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:
/// - 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(_ method: HTTPMethod = .GET, secureSocketPath: String, urlPath: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture<Response> {
do {
guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: urlPath) 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)
}
Expand Down
34 changes: 32 additions & 2 deletions Sources/AsyncHTTPClient/HTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ extension HTTPClientInternalTests {
("testUploadStreamingIsCalledOnTaskEL", testUploadStreamingIsCalledOnTaskEL),
("testWeCanActuallyExactlySetTheEventLoops", testWeCanActuallyExactlySetTheEventLoops),
("testTaskPromiseBoundToEL", testTaskPromiseBoundToEL),
("testInternalRequestURI", testInternalRequestURI),
]
}
}
27 changes: 27 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
5 changes: 5 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading