How to create a URL from a host and port?

I want to connect to irc.libera.chat:6697 via plain TCP/IP, so it's not HTTP(S) or Web Sockets or anything like that. Libera is a IRC server.
I was initially thinking:

let url = URL(string: "irc.libera.chat:6697")!

But it's behaving sort of strange, like accessing the port property from url yields nil:

url.port // nill, expected 6697

First, you'll want to shove this into URLComponents, then you'll grab the optional URL from that.

Ex:

guard let components = URLComponents(string: "irc://irc.libera.chat:6697") else {
  return
}

// Port
let port: Int?  = components.port

// URL
let url: URL? = components.url

Hope this clears up some confusion!

Just add a (dummy) scheme and the parsing will correctly identify it:

  6> let url = URL(string: "tcp://irc.libera.chat:6697")!
url: Foundation.URL = "tcp://irc.libera.chat:6697"
  7> url.port
$R2: Int? = 6697
1 Like

Others have addressed your URL question but I’m confused by this:

I want to connect to irc.libera.chat:6697 via plain TCP/IP

If that’s the case, why do you need a URL? Most TCP APIs don’t take URLs. And the ones that do generally define their own scheme (tcp: is a popular one) to use in the URLs that you pass them.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Hi @eskimo, well, I'm kind of new to Swift and still figuring things out. I'm using URLSession's URLSessionStreamTask, that indeed only takes a host and a port, but I wanted to have them grouped together instead of making a struct with two fields, or a tuple.

I was kind of looking at URL's resourceBytes and lines property and I was wondering if it's no possible for me to communicate with Libera Chat that way, because if I do:

let url = URL("tcp://irc.libera.chat:6697")!

for await line in url.lines {
    print(line)
}

I actually get some IRC protocol output, not sure why, because AFAIK, url.lines, should just do a HTTP GET on the URL, right? Yet IRC is not a HTTP based protocol.

If you're interested to see what I'm doing checkout my GitHub repo

I'm not familiar with any specifics of IRC, but as a general rule in Swift programming design, do not be afraid to make structs that group together semantically relevant values. It is really a useful organisational technique, and also helps prevent silly logic mistakes in some cases.

1 Like

instead of making a struct with two fields, or a tuple.

As bzamayo said, creating your own struct or tuple is fine for this. A lot of the time you end up needing something like that to carry other information, like whether to use TLS or not. You could pack all that info into a URL but it’ll be less convenient to get it back out again.

I actually get some IRC protocol output, not sure why, because AFAIK,
url.lines, should just do a HTTP GET on the URL, right?

What platform are you running this on? I tried this on macOS 12.2 and the code threw an NSURLErrorUnsupportedURL error, which is what I’d expect because NSURLSession does not support tcp: URLs. If you’re on Linux then it might work because, on Linux, URLSession is implemented in terms of curl and, while I’m certainly not an expert on curl, it wouldn’t surprise me if it had tcp: URL support.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

I'm on macOS 12.3

I certainly hope that doesn't work.

URLSession is a universal client - applications can plug-in to it by registering subclasses of URLProtocol, which also allows them to add arbitrary data to URLRequests. In fact, it even supports loading requests when the URL is nil.

The cURL part of things comes about because URLSession includes default handlers for requests to particular URL schemes, such as http/s. If there is no registered handler for a request at the application level, the request should fail and return an unsupportedURL error (although SCF currently does not match Darwin behaviour and crashes - see this PR).

Regardless, it should not just be throwing things at cURL and seeing what gets accepted/rejected.

You should go with the struct or tuple.

The way applications like this usually work is that the URL expresses things such as the host and port (and other data), and the first thing the application does is extract those as separate values. The URL is just a convenient string format which lets you pack that information together -- but for actually processing them, you'll want to split them in to separate pieces of data.

The irc: (and ircs:) URL schemes are not incredibly well-defined, but they allow packing in more information than just the host and port. I managed to find a couple of websites describing the generally-accepted format and options, by googling the phrase "irc URL scheme":

You may want to eventually support some of the other data from those formats. For example:

Connect to #channel1 and #channel2 using SSL:

ircs://irc.example.com:6697/#channel1,#channel2 

Again, you'd start with a URL (perhaps from an external source, like Safari), unpack the data in to a struct, then use it to figure out which host to connect to which channels to connect to, etc.

I'm on macOS 12.3

OK, then packing the host and port into a URL is pointless because all of macOS’s TCP APIs take those as separate values.

On the Swift concurrency front, I’m sad to say that none of macOS’s TCP APIs support that out of the box. You could write a shim that implements this on top of an existing API — for example, by combining Network framework with Swift concurrency’s continuation support — but that’s a non-trivial amount of work.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple