Skip to content

Support UNIX Domain Sockets #151

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 10 commits into from
Jan 27, 2020
Merged

Support UNIX Domain Sockets #151

merged 10 commits into from
Jan 27, 2020

Conversation

krzyzanowskim
Copy link
Contributor

@krzyzanowskim krzyzanowskim commented Jan 19, 2020

Adds support for UNIX Domain Socket requests.

Usage:

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
let socketURL = URL(string: "unix:///var/run/docker.sock")!
let req = try HTTPClient.Request(url: URL(string: "/users/list", relativeTo: socketURL)!, method: .GET)
let response = try httpClient.execute(request: req).wait()

@swift-server-bot
Copy link

Can one of the admins verify this patch?

2 similar comments
@swift-server-bot
Copy link

Can one of the admins verify this patch?

@swift-server-bot
Copy link

Can one of the admins verify this patch?

Copy link
Collaborator

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this patch!

I don't think we should use file:// as the scheme for Unix domain sockets: file:// is a very well-understood scheme from the perspective of browser user agents, and overloading that semantic here for another purpose seems unwise. I think the traditional choice for a scheme for Unix sockets is unix://, which works better. We could also choose to use unix+http://, but I think I'd begin with unix:// for now: we can always widen the list.


struct RedirectState {
var count: Int
var visited: Set<URL>?
}

var redirectState: RedirectState?
private var value: HTTPRequest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love using a protocol existential here. I think the splitting of basic request types is a good idea, but I'd rather use an enumeration that we can switch over than the protocol existential.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the only difference between implementations is how they handle scheme? Why not just add a unix:// to a list of supported schemes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just add a unix:// to a list of supported schemes?

slightly different asserts in Request constructor

Copy link
Contributor Author

@krzyzanowskim krzyzanowskim Jan 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather use an enumeration that we can switch over than the protocol existential.

I'm not a fan. Do you think about if'ing in every Request property?

I've rework it with enum: d3bbf37 let me know if that's something you love more @Lukasa 😅

@artemredkin
Copy link
Collaborator

Two questions:

  1. What is the use-case you have in mind? Never personally encountered it, just curious
  2. Cory, could ssl negotiation be run on top of the unix socket? Is there anything tcp/ip specific? Is it something potentially useful?

@krzyzanowskim
Copy link
Contributor Author

What is the use-case you have in mind?

My use case is local Docker server over unix socket

@artemredkin
Copy link
Collaborator

Point 2 would probably not be useful since we running it on the same machine...

@krzyzanowskim
Copy link
Contributor Author

could ssl negotiation be run on top of the unix socket?

in general yes. I don't know if nio can handle it though. Looks like eg. postgres can utilise that

@Lukasa
Copy link
Collaborator

Lukasa commented Jan 20, 2020

Cory, could ssl negotiation be run on top of the unix socket? Is there anything tcp/ip specific? Is it something potentially useful?

You can run TLS over the unix socket, it's mostly not useful though it can be if the process on the other side of the unix socket is a TCP proxy. It's worth keeping it as a thing we could potentially support in future.

in general yes. I don't know if nio can handle it though.

NIO's TLS implementation is 100% composable, so it can be run over literally any stream transport. 😉

self.kind = .unixSocket
self.host = ""
} else {
throw HTTPClientError.unsupportedScheme(scheme)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block can be rewritten to be substantially cleaner by defining some helpers:

extension Kind {
    private static var hostSchemes = ["http", "https"]
    private static var unixSchemes = ["unix"]

    init(forScheme scheme: String) throws {
        if Kind.hostSchemes.contains(scheme) {
            self = .host
        } else if Kind.unixSchemes.contains(scheme) {
            self = .unixSocket
        } else {
            throw HTTPClientError.unsupportedScheme
        }
    }

    func hostFromURL(_ url: URL) throws -> String {
        switch self {
        case .host:
            guard let host = url.host else {
                throw HTTPClientError.emptyHost
            }
            return host
        case .unixSocket:
            return ""
        }
    }
}

This code then becomes:

self.kind = try Kind(scheme: scheme)
self.host = try self.kind.hostFromURL(url)

We can then also rewrite kind.isSchemeSupported in terms of our statics. This should put most of the complexity into the enum definition, which helps keep the code elsewhere a lot nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the feedback. Applied as requested.

Copy link
Collaborator

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I think I'm happy with this. @weissi what do you think?

@krzyzanowskim krzyzanowskim marked this pull request as ready for review January 27, 2020 13:27
Copy link
Contributor

@weissi weissi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much. Looks good to me

@weissi
Copy link
Contributor

weissi commented Jan 27, 2020

@swift-server-bot test this please

@weissi weissi requested a review from artemredkin January 27, 2020 13:37
Copy link
Collaborator

@artemredkin artemredkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question, otherwise LG

@weissi
Copy link
Contributor

weissi commented Jan 27, 2020

sorry @krzyzanowskim , this repo is set up with an formating requirements :|

=> Checking format... formatting issues!
diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift
index bb93908..dd50433 100644
--- a/Sources/AsyncHTTPClient/HTTPClient.swift
+++ b/Sources/AsyncHTTPClient/HTTPClient.swift
@@ -290,12 +290,12 @@ public class HTTPClient {
         }
 
         eventLoopChannel.map { channel in
-              task.setChannel(channel)
-            }
-            .flatMap { channel in
-                channel.writeAndFlush(request)
-            }
-            .cascadeFailure(to: task.promise)
+            task.setChannel(channel)
+        }
+        .flatMap { channel in
+            channel.writeAndFlush(request)
+        }
+        .cascadeFailure(to: task.promise)
 
         return task
     }
diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift
index eb86ee2..fc83eac 100644
--- a/Sources/AsyncHTTPClient/HTTPHandler.swift
+++ b/Sources/AsyncHTTPClient/HTTPHandler.swift
@@ -88,7 +88,6 @@ extension HTTPClient {
 
     /// Represent HTTP request.
     public struct Request {
-
         /// Represent kind of Request
         enum Kind {
             /// Remote host request.
@@ -111,13 +110,13 @@ extension HTTPClient {
 
             func hostFromURL(_ url: URL) throws -> String {
                 switch self {
-                    case .host:
-                        guard let host = url.host else {
-                            throw HTTPClientError.emptyHost
-                        }
-                        return host
-                    case .unixSocket:
-                        return ""
+                case .host:
+                    guard let host = url.host else {
+                        throw HTTPClientError.emptyHost
+                    }
+                    return host
+                case .unixSocket:
+                    return ""
                 }
             }
 
@@ -844,7 +843,7 @@ internal struct RedirectHandler<ResponseType> {
             return nil
         }
 
-        guard request.kind.supports(scheme: self.request.scheme) else {
+        guard self.request.kind.supports(scheme: self.request.scheme) else {
             return nil

mind running SwiftFormat on it?

@lou-lan
Copy link

lou-lan commented Jan 27, 2020

I am test it, got some error:

Fatal error: Error raised at top level: HTTPClientError.unsupportedScheme("file"): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1100.8.280/swift/stdlib/public/core/ErrorType.swift, line 200
2020-01-27 21:46:57.444788+0800 Demo[30365:399706] Fatal error: Error raised at top level: HTTPClientError.unsupportedScheme("file"): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1100.8.280/swift/stdlib/public/core/ErrorType.swift, line 200
(lldb) 
import Foundation
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
let socketURL = URL(fileURLWithPath: "/var/run/docker.sock")
let req = try HTTPClient.Request(url: URL(string: "/_ping", relativeTo: socketURL)!, method: .GET)
let response = try httpClient.execute(request: req).wait()
print(response)

OS: macOS
Swift version 5.1.3

@weissi
Copy link
Contributor

weissi commented Jan 27, 2020

@swift-server-bot add to whitelist

@weissi weissi requested a review from artemredkin January 27, 2020 13:48
@weissi
Copy link
Contributor

weissi commented Jan 27, 2020

@lou-lan try unix:///path/to/file instead of file:///...

@lou-lan
Copy link

lou-lan commented Jan 27, 2020

@weissi I got the same error message:

Fatal error: Error raised at top level: HTTPClientError.unsupportedScheme("file"): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1100.8.280/swift/stdlib/public/core/ErrorType.swift, line 200
2020-01-27 22:01:34.073115+0800 Demo[31194:417905] Fatal error: Error raised at top level: HTTPClientError.unsupportedScheme("file"): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1100.8.280/swift/stdlib/public/core/ErrorType.swift, line 200
import Foundation
import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
let socketURL = URL(fileURLWithPath: "unix:///var/run/docker.sock")
let req = try HTTPClient.Request(url: URL(string: "/_version", relativeTo: socketURL)!, method: .GET)
let response = try httpClient.execute(request: req).wait()
print(response)

截屏2020-01-2722 05 03

@krzyzanowskim
Copy link
Contributor Author

@lou-lan I updated example in description. Don't use fileURLWithPath, it overrides your URL schema

@lou-lan
Copy link

lou-lan commented Jan 27, 2020

@krzyzanowskim Success, thank you very much, very cool PR.

@artemredkin artemredkin added the 🆕 semver/minor Adds new public API. label Jan 27, 2020
@artemredkin artemredkin added this to the 1.1.0 milestone Jan 27, 2020
@weissi weissi merged commit e90f5fd into swift-server:master Jan 27, 2020
@PopFlamingo PopFlamingo mentioned this pull request Feb 2, 2020
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🆕 semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants