NIOAsyncChannel HTTP1 Server Setup

I'm playing with the new NIOAsyncChannel and have some problems configuring a HTTP1 server to use it. I have this, is this roughly correct? :slight_smile:

typealias ClientChannelType = 
  NIOAsyncChannel<HTTPServerRequestPart, HTTPServerResponsePart>

let serverChannel = try await bootstrap
  .bind(host: "localhost", port: port) { channel in
    channel.pipeline.configureHTTPServerPipeline()
      .flatMap {
        channel.eventLoop.makeCompletedFuture {
          try ClientChannelType(wrappingChannelSynchronously: channel)
        }
      }
  }
}

This raises sendability concerns in IOData, which is explicitly marked as non-sendable. Presumably because of FileRegion?

Yes that’s in general the right shape. IOData can sadly not conform to Sendable due to the file region it could potentially hold. I would recommend using the new handlers that convert to the standard http types which also neatly get rid of the Sendable problem.

A small nit with your code above: You can use the syncOperations of the channels pipeline to add the handlers which lets you avoid all of the future chaining and you can just wrap it all in a channel.eventLoop.makeCompletedFuture

1 Like

Thanks, I assume that would be this just for reference:

typealias ClientChannelType = 
  NIOAsyncChannel<HTTPServerRequestPart, HTTPServerResponsePart>

let serverChannel = try await bootstrap
  .bind(host: "localhost", port: port) { channel in
    channel.eventLoop.makeCompletedFuture {
      try channel.pipeline.configureHTTPServerPipeline()
      return try ClientChannelType(wrappingChannelSynchronously: channel)
    }
  }
}

Hm, the "new handlers" are in a separate package. Do you plan to integrate this stuff w/ NIO in the 2.x cycle?

Yes that code snippet looks correct. Right now there are no plans to integrate this into NIO itself. One of the reasons why it is in nio-extras is that it also provides similar handlers for http2 which is already in a separate repo. Is there any reason why you cannot depend on nio-extras?

Not really, I would just like to keep the dependencies small. It is a little unfortunate if you can't accomplish the same functionality w/ concurrency that works w/o it, w/o having to add another package.
But sure, it is not a huge deal.

(extras also has a ton of code I'm not interested in (for this), NFS, PCAP, SOCKS, ..., and also itself has extra deps like the http/2 package).

So if you just want to get rid of the warning you can do this yourself with just NIO by putting in this handler.

Regarding the dependency concern, while NIO extras brings in a few more dependencies non of them are going to get checked out since Swift PM is doing a target based dependency resolution. So if you just use the HTTP1 converters you should even checkout NIOHTTP2.

2 Likes

For some context, I'd like to make a variant of my A µTutorial on SwiftNIO that uses the new NIOAsyncChannel stuff.
It's a little annoying having to tell people that just SwiftNIO is insufficient, but that they have to add one extra to get this going (or add own utilities like the type wrapper, same for DiscardingTaskGroup btw).

one reason to avoid additional repo dependencies (as a library) simply for the sake of avoiding repo dependencies is to avoid restricting downstream users who may also want to depend on that same repo. this is one of the reasons i started phasing out swift-argument-parser from libraries, because of complaints that it was preventing folks from using their desired version of swift-argument-parser.

That's just the Swift iteration of DLL hell, but doesn't actually relate to my concerns.

i know, i was just mentioning it in passing because i have also been frustrated with the fractured nature of the SwiftNIO libraries.

Yes, but my complaint about extras here is that it is not fractured enough :slight_smile: Things like NFS should really be own packages. (it is a style/taste question, e.g. I also dislike having all the examples in the core NIO repo, that IMO just spams the package)

But even that is separate, my main issue is feature disparity between regular NIO and concurrency in the base library :woman_shrugging:

but this makes the libraries much harder to test, because each component has an independently-incrementing version series. it will never be possible to test enough of the (exponential) combinations of versions people could be using, so you end up with a lot of it works on my machine ¯_(ツ)_/¯.

there is not going to be any overhead associated with the unused targets in a package dependency in the compiled binaries, so it just doesn’t seem to be a worthy tradeoff in my opinion.

i can think of at least two things NIO could do to alleviate this problem:

  1. NIO could move the examples to a nested swift package in the same repo, like it is already doing for its benchmarks.

  2. NIO could convert some of the simpler examples into SPM Snippets, which were designed for exactly this situation and would go a long way towards reducing the manifest clutter.

@FranzBusch WDYT?

in general i have found the newer channel interfaces to have fewer problems with swift concurrency than the old handler interfaces.

one thing i would suggest (for NIO) is promoting the HTTPByteBufferResponsePartHandler mentioned earlier to an actual library API. i actually re-invented the same type about a month ago when i was dealing with the same IOData-concurrency problem, so there’s definitely a need here.

This has gotten way too much off-topic for my interests.
Even for a production system I want as little code as possible, i.e. not the npm situation. If I build an HTTP1 web server, I most definitely don't want any NFS code anywhere in sight :slight_smile: (regardless how cool it is, and I think nio-nfs is really cool)

Just to take a step back. From purely an adopter perspective there really shouldn't be a difference in having all of the NIO functionality in one package or split across multiple ones when it comes to code size or compilation time. Swift PM is only building/linking the targets that are needed. Furthermore, it is only checking out transitive dependencies when products are used that need them.

Now coming to the NIO specific questions. The reason why NIO ended up with a bit of a fragmented package setup is mainly due to historic reasons. NIOSSL and NIOHTTP2 had dependencies on C libraries. Those were problematic in the early days of Swift PM where it wasn't doing target based dependency resolution. I can't speak for the rest of the NIO team but if we were to do it again we would probably move most of it into a single repository again since it provides quite a few benefits such as better documentability, easier testing & examples, and an easier release process. However, the one benefit that individual repos have is that they allow for individual versioning.

Nesting benchmarks in a sub package was mostly done to avoid bringing the dependency to all adopters. While Swift PM does target based resolution it is very easy to break it. We would love to see Swift PM gain the ability to express optional features similar to what cargo offers which allows us to express development dependencies.
I, personally, would love to move all of our examples into a nested Examples folder but keep them in the main package. This brings the benefits of "getting them out of the way" while making sure during development one is building all of code.

Last I checked snippets weren't fully supported by DocC but I might be mistaken here (I haven't tried for a long time). I do agree though that snippets are an amazing feature that would should try to pick up.

We definitely need to do something here. If that is providing a new configure method that inserts the handler or if we just drop IOData completely in a hypothetical NIO3 hasn't been decided yet.

Appreciate all the discussion here and please always feel free to file issues about those things in our repos!

1 Like

Yes, that's not the primary concern, but code review (though I still think it is relevant, and fwiw I don't trust SPM or whatever Xcode variant is active, just out of experience).
Obviously it is not an issue for Apple, because it is all own code that doesn't require review :slight_smile:

I.e. to give an example, if I use a dep in a production project, I'm responsible for it. If I use a big project like extras, I'm going to have to review changes to NFS and SOCKS and whatnot, which are completely irrelevant to what I do. I may have to live with that, but I don't want that.
In a production project the first thing I would probably do first is fork, delete all the extras and track just the subset I actually need. It is just a matter of responsibility :woman_shrugging:

P.S.: I do not expect any changes here, just outlining my personals thoughts about it, I'm aware I have to deal with it.

i’m not sure what the current state of DocC snippets is, but Snippets by themselves are still incredibly useful even with no DocC integration at all.

i recall when the feature was first pitched, it was conceived almost entirely as a documentation aid, but ironically i have found Snippets most useful for testing and benchmarking and rarely use them for documentation purposes. Snippets really are incredible for when you just want a swift module without going through the whole process of setting up a swift module.

Nice, that works. Another thing I wondered, previously I think I had to call writeAndFlush to make sure that the content is actually written to the socket. NIOAsyncChannelOutboundWriter doesn't seem to have that anymore, are the writes always flushed?

Yes. If you want to write multiple elements with only one flush at the end use write(contentsOf:).

1 Like

The issue has already been resolved above?