# \[Feedback\] RedisNIO: A NIO-Based Redis Driver

**URL:** <https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521>\
**Category:** Proposals\
**Tags:** server, redis\
**Created:** [June 10, 2019, 4:54am UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521 "2019-06-10T04:54:20Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [June 10, 2019, 4:54am UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/1 "2019-06-10T04:54:20Z")

</div>

# RedisNIO: A NIO-based Redis Driver

The review of [SSWG-0004: RedisNIO Driver](https://github.com/swift-server/sswg/blob/dfc128f7273f8c1969eb9d963d255284977a536b/proposals/0004-nio-redis.md) starts now and runs through **June 26, 2019**.

> This package has the git tag **1.0.0-alpha.1** for the purposes of providing a quick way to pull the implementation into projects for evaluation.

| | |
| --- | --- |
| **Package Name** | `swift-redis-nio-client` |
| **Module Name** | `RedisNIO` |
| **Proposed Maturity Level** | [Incubating](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) |
| **License** | [Apache 2](https://www.apache.org/licenses/LICENSE-2.0.html) |
| **Dependencies** | [SwiftNIO](https://github.com/apple/swift-nio) 2.x, [SwiftLog](https://github.com/apple/swift-log) 1.x, [SwiftMetrics](https://github.com/apple/swift-metrics) 1.x |
| **Project Repo** | [Swift on Server Community / RediStack · GitLab](https://gitlab.com/mordil/swift-redis-nio-client) ([GitHub Mirror](https://github.com/mordil/swift-redis-nio-client)) |

### What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the evolution of the server-side Swift ecosystem.

When reviewing a proposal, here are some questions to consider:

- What is your evaluation of the proposal?
- Is the problem being addressed significant enough?
- Does this proposal fit well with the feel and direction of Swift on Server?
- If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
- How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for contributing to the Swift Server Work Group!

### What happens if the proposal gets accepted?

If this proposal gets accepted, the repository will then become usable as a SwiftPM package and a version (likely `1.0.0`) will be tagged. The development (in form of pull requests) will continue as a regular open-source project.

---

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [June 10, 2019, 8:04pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/2 "2019-06-10T20:04:51Z")

</div>

This looks great! Huge +1 to this proposal and the desired sandbox maturity level.

I have some comments on the proposal below. It's mostly just small things that I think could make the API more consistent w/ Swift and NIO. But, as it stands, I think this proposal is good to move forward regardless of whether these changes are made.

Thanks, @Mordil !

* * *

### `RESPTranslator` as a struct

We may want to add some configuration options for RESP translation in the future. If `RESPTranslator` were a struct with instance methods, we could pass those options in during init without needing to change the parsing / serialization API. This would give us more flexibility in the future.

### `RESPTranslator.parseBytes` using position

Changing the current reader index of a `ByteBuffer` is a very cheap operation. Generally, you should always use the `read...` prefixed methods that mutate a buffer instead of the `get` methods plus a custom offset.

Because of this, I think we should expect that the buffer supplied to `RESPTranslator.parseBytes` has the message as the first readable byte, so that we can omit the `fromIndex: inout Int`. This would simplify the API by relying on the great work the NIO team has already done in ByteBuffer.

### `RESPValueConvertible` API

I think this package could benefit from a convertible-protocol pattern that Vapor's DB modules are using:

```swift
public protocol FooConvertible {
    init?(foo: Foo)
    var foo: Foo? { get }
}

```

There are a few key differences with this pattern compared to `RESPValueConvertible`:

1: The init method uses a label. Given that very heavily used types like `String` and `Int` will conform, we should probably avoid overloading method names like `init(_:)` too much.  
2: The convert-to method allows returning `nil` if the conversion fails (just like the init method does)  
3. This more closely matches patterns already found in Swift like `CustomStringConvertible`.

### Bootstrap helpers as extensions on NIO types

NIO at least offers bootstrap helpers (like `addHTTPHandlers()`) as methods on NIO types like `ChannelPipeline`. Perhaps this package should consider matching that instead of using static methods on the `Redis` type.

### sub-class vs. protocol

I'd personally rather see `RedisCommandHandler` being a protocol w/ default extensions than an `open` class. IMO inheritance is only really appropriate for Objective-C compatible development (like w/ UIKit, AppKit, etc). With pure Swift, protocols + extensions can do the same job, while also being composable.

### `Redis.makeConnection`

I think this method would make more sense on `RedisConnection` itself. If I were attempting to create a new `RedisConnection` in my code (without reading the docs first), I would probably write something like:

```swift
import RedisNIO

let conn = RedisConnection.

```

After writing this, I'd inspect the auto-completion options available. At the current state, I'd see the `init(channel:logger:)` method which I might think to use, but not the actual method I need.

If the `makeConnection` (maybe call it `connect`?) method were statically available on `RedisConnection`, I would find it immediately.

Potentially `init(channel:logger:)` could consider being made internal, too. There's really no reason to use it outside of testing, where one could use `@testable import` anyway.

---

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [June 12, 2019, 6:47am UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/3 "2019-06-12T06:47:59Z")

</div>

> For all items agreed to update, I've created [swift-redis-nio-client !47](https://gitlab.com/Mordil/swift-redis-nio-client/issues/47) for tracking.

> [@tanner0101](#):
>
> I'd personally rather see `RedisCommandHandler` being a protocol w/ default extensions than an `open` class. IMO inheritance is only really appropriate for Objective-C compatible development (like w/ UIKit, AppKit, etc). With pure Swift, protocols + extensions can do the same job, while also being composable.

I was going to refute this as a direct contradiction made in the [initial pitch thread](https://forums.swift.org/t/swiftnio-redis-client/19325/2) - but after re-reading the feedback I'm reinterpreting what was said and struggling to find out why I went the route of `open` for the means of extension that Johannes was referring to. I'll definitely change this as I was not too keen personally on it being open either.

> [@tanner0101](#):
>
> We may want to add some configuration options for RESP translation in the future. If `RESPTranslator` were a struct with instance methods, we could pass those options in during init without needing to change the parsing / serialization API. This would give us more flexibility in the future.

This is true, but I don't see it being a huge nuisance to change from an enum to a struct in order to do that later (aside from it being major vs minor SemVer bump).

If it was changed, how would we feel about trying to mimic the `Codable` API of `encode(_:) -> RESPValue` and `decode<Int | String | Buffer | etc>(from: RESPValue) -> T`?

> [@tanner0101](#):
>
> Changing the current reader index of a `ByteBuffer` is a very cheap operation. Generally, you should always use the `read...` prefixed methods that mutate a buffer instead of the `get` methods plus a custom offset.

This is probably a question for the NIO team...

What happens if we move the reader index during say, a decoding pass of an array (so it'll be recursive) but then return `.needMoreData` in `ByteToMessageDecoder`? Do we get a "reset" bytebuffer each time, or will I need to now build state for the decoder?

My understanding is no, we won't.

My intention for `RESPTranslator` was for it to be more of an "observer" of the ByteBuffer for just parsing `RESPValues` out of

> [@tanner0101](#):
>
> ### `Redis.makeConnection`
> 
> I think this method would make more sense on `RedisConnection` itself.

> [@tanner0101](#):
>
> ### Bootstrap helpers as extensions on NIO types

.I originally had these, but then moved to the `Redis` namespacing enum to provide a pattern for higher level libraries to provide extensions on a common "type" - but I just realized this reduces the ability for Vapor to provide a top-level module named `Redis`.

I've added this to my list to again break these out to existing constructs.

- `RedisConnection.connect(to:...)`
- `ClientBootstrap.makeDefaultForRedis(...)`

> [@tanner0101](#):
>
> There are a few key differences with this pattern compared to `RESPValueConvertible` :
> 
> 1: The init method uses a label. Given that very heavily used types like `String` and `Int` will conform, we should probably avoid overloading method names like `init(_:)` too much.  
> 2: The convert-to method allows returning `nil` if the conversion fails (just like the init method does)  
> 3. This more closely matches patterns already found in Swift like `CustomStringConvertible` .

1 - That is fair, and I've re-read and understood API Design Guidelines (man, they're tough to get right all the time 😄) - I'll update to include a label

2 & 3 - `RESPValue` is different from `PostgresDataType` and `PostgresData` in that the backing values are one prime primitive (String / Integer) for their underlying type - so they have a solid conversion between the two that one would expect to match the pattern of `ExpressibleBy*Literal` protocols. Given that `RESPValue` has a representation of what `nil` would look like with `null`, it's reasonable to expect conformers to just return `nil` if they can't express their value within a RESPValue.

While `PostgresDataType` does have a concept of "null" - it still has _a_ value that can be represented elsewhere with the storage of the Int value but `RESPValue`'s "null" is just that - it's a single representation of _no value at all_.

---

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [June 12, 2019, 4:13pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/4 "2019-06-12T16:13:49Z")

</div>

> [@Mordil](#):
>
> What happens if we move the reader index during say, a decoding pass of an array (so it'll be recursive) but then return `.needMoreData` in `ByteToMessageDecoder` ? Do we get a "reset" bytebuffer each time, or will I need to now build state for the decoder?

In this situation, create a temporary copy of the buffer first and make `read` calls on that. If the parsing succeeds (not the `.needMoreData` case), then you can set the inout buffer to the copy via `buffer = copy`.

Calling `read...` methods will never make a copy of the underlying storage. It's only a very small struct containing the offsets that is ever copied.

In other words, the custom offset being passed to `RESPTranslator` is doing exactly what `ByteBuffer`'s current read index should be doing.

See an example here: [https://github.com/vapor/postgres-nio/blob/master/Sources/PostgresNIO/Message/PostgresMessageDecoder.swift#L20-L50](https://github.com/vapor/postgres-nio/blob/master/Sources/PostgresNIO/Message/PostgresMessageDecoder.swift#L20-L50)

> [@Mordil](#):
>
> Given that `RESPValue` has a representation of what `nil` would look like with `null` , it's reasonable to expect conformers to just return `nil` if they can't express their value within a RESPValue.

I still think the distinction between `RESPValue.null` and `RESPValue?.none` would be useful in the future, especially considering the convert method is not throwing. But I'll defer to you here 👍

---

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [June 12, 2019, 5:18pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/5 "2019-06-12T17:18:32Z")

</div>

> [@tanner0101](#):
>
> In this situation, create a temporary copy of the buffer first and make `read` calls on that. If the parsing succeeds (not the `.needMoreData` case), then you can set the inout buffer to the copy via `buffer = copy` .

... I don't know why I didn't think of that. 🤦‍♂️

I'll add this to the list.

> [@tanner0101](#):
>
> I still think the distinction between `RESPValue.null` and `RESPValue?.none` would be useful in the future, especially considering the convert method is not throwing. But I'll defer to you here

I'm willing to listen others to see if the majority opinion differs from mine. If I'm in the minority of seeing a distinction without a purpose I'm willing to change it to match the majority's expectation.

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [June 21, 2019, 4:55pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/6 "2019-06-21T16:55:11Z")

</div>

Thanks @Mordil for your great work on this and sorry for my late review (excuse: WWDC/try! Swift SJC-related California travel 😛).

This looks really good already. I just had a quick look at the code and have a few comments. First some things that I think should definitely be fixed and then a list of random small things that caught my eye whilst reading some of the code (which I submitted an [issue](https://gitlab.com/Mordil/swift-redis-nio-client/issues/48)). The nits/code review shouldn't be taken as proposal feedback because I think that's all stuff that can be fixed later and doesn't affect the public API.

Proposal review:

- Given that this is a new package, I'd suggest 'sandbox' level instead of 'incubating'. At the moment you are pretty much the only contributor which IMHO is fine for 'sandbox' but doesn't meet the minimum requirements. I also wonder if choosing gitlab instead of github is a good choice given that we should get some more contributors for this package over time. I just had a to create a gitlab account...
- The documentation for `RedisClient.expire` seems to confuse 'deadlines' (a point in time such as "12st Feb 2020 at 12:13:14Z") with timeouts (a time interval from "now", example "5 seconds")
- I think, the API of `makeConnection` leaks an `EventLoopGroup` and therefore a thread per call, we need to fix this.
- I'm not in love with the name `RedisCommandContext` because really it's just a command and its response promise. What about `RedisCommand` or `RedisCommandExecution`? Also please add to the documentation that this type does not have value semantics (it's a `struct` so people might expect that).
- can we please get rid of all the `withUnsafe*Bytes`? Swift is a memory-safe language unless you use `*unsafe*`. Especially network parsers should stay clear of using this because that brings us back straight back to the unsafe C land. If there's a concept concept that you cannot express with `ByteBuffer`'s APIs that don't contain `*unsafe*`, please file a bug. (That is unless you have to interface with C of course where you'll need the `*unsafe*` functions).
- `RedisByteDecoder` looks like a fairly complex `ByteToMessageDecoder` which is okay but please make use of [`ByteToMessageVerifier`](https://github.com/apple/swift-nio/blob/master/Sources/NIOTestUtils/ByteToMessageDecoderVerifier.swift#L55) to make sure it passes some basic tests. [Example use here](https://github.com/apple/swift-nio-extras/blob/master/Tests/NIOExtrasTests/LineBasedFrameDecoderTest.swift#L191-L215) but there are more in `swift-nio` and `swift-nio-extras`. I'm pretty sure I already saw a bug related to partial incoming responses from Redis but `ByteToMessageVerifier` will test that automatically. Any questions regarding its use, please reach out.
- any reason for the API being `RESPTranslator.writeValue(_ value: RESPValue, into out: inout ByteBuffer)` instead of the more common

```swift
extension ByteBuffer {
    public mutating func writeRESPValue(_ value: RESPValue) {
        ...
    }
}

```

?

---

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [June 21, 2019, 10:13pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/7 "2019-06-21T22:13:34Z")

</div>

> [@johannesweiss](#):
>
> Given that this is a new package, I'd suggest 'sandbox' level instead of 'incubating'. At the moment you are pretty much the only contributor which IMHO is fine for 'sandbox' but doesn't meet the minimum requirements.

This is 100% fair, and I'm deferring all decisions to the SSWG. I had debated on dropping this section and leaving it as 'Sandbox', but didn't for whatever reason made sense to me at the time 🙂

There will be no hurt feelings.

> [@johannesweiss](#):
>
> The documentation for `RedisClient.expire` seems to confuse 'deadlines' (a point in time such as "12st Feb 2020 at 12:13:14Z") with timeouts (a time interval from "now", example "5 seconds")

Ah, great catch - and an important distinction. I'll update to properly match Redis' usage of purely _timeout_.

> [@johannesweiss](#):
>
> I think, the API of `makeConnection` leaks an `EventLoopGroup` and therefore a thread per call, we need to fix this.

Correct, this was a naïve implementation. [#52](https://gitlab.com/Mordil/swift-redis-nio-client/issues/54) tracks this bug

> [@johannesweiss](#):
>
> I'm not in love with the name `RedisCommandContext` because really it's just a command and its response promise. What about `RedisCommand` or `RedisCommandExecution` ? Also please add to the documentation that this type does not have value semantics (it's a `struct` so people might expect that).

`RedisCommand` sounds about right. Do you think it being a struct still makes sense (as long as it's documented clearly that it's meant to have reference semantics), or should it become a `class`?

> [@johannesweiss](#):
>
> can we please get rid of all the `withUnsafe*Bytes` ? Swift is a memory-safe language unless you use `*unsafe*` . Especially network parsers should stay clear of using this because that brings us back straight back to the unsafe C land. If there's a concept concept that you cannot express with `ByteBuffer` 's APIs that don't contain `*unsafe*` , please file a bug. (That is unless you have to interface with C of course where you'll need the `*unsafe*` functions).

This is probably where a 2nd contributor to do code reviews probably would've been helpful. I might have gotten too "clever" (in my own eyes) without some due diligence into risk / benefits analysis.

I know of a few places where I can easily replace this, and I'll review other locations as well. [#55](https://gitlab.com/Mordil/swift-redis-nio-client/issues/55) tracks this; please feel free to add to the list that you saw.

> [@johannesweiss](#):
>
> `RedisByteDecoder` looks like a fairly complex `ByteToMessageDecoder` which is okay but please make use of [`ByteToMessageVerifier`](https://github.com/apple/swift-nio/blob/master/Sources/NIOTestUtils/ByteToMessageDecoderVerifier.swift#L55) to make sure it passes some basic tests. [Example use here](https://github.com/apple/swift-nio-extras/blob/master/Tests/NIOExtrasTests/LineBasedFrameDecoderTest.swift#L191-L215) but there are more in `swift-nio` and `swift-nio-extras` . I'm pretty sure I already saw a bug related to partial incoming responses from Redis but `ByteToMessageVerifier` will test that automatically. Any questions regarding its use, please reach out.

This is fantastic! I wasn't aware of this - [#56](https://gitlab.com/Mordil/swift-redis-nio-client/issues/56) tracks this.

If you're referring to the issue that was caught by the Vapor/Jobs package, I think that issue would have still come up even with `ByteToMessageVerifier` tests, simply due to a gap in types and over-eager generics.

> [@johannesweiss](#):
>
> any reason for the API being `RESPTranslator.writeValue(_ value: RESPValue, into out: inout ByteBuffer)` instead of the more common

This plays into the goal of refactoring `RESPTranslator` - and I'm open to discussion on this.

Do we want a standalone encoder/decoder that we instantiate, perhaps with some sort of configuration like @tanner0101 suggested - or do we want extensions on `ByteBuffer`? I could see the benefits of both, or one over the other.

I'm just looking for consensus 🙂

> [@johannesweiss](#):
>
> I also wonder if choosing gitlab instead of github is a good choice given that we should get some more contributors for this package over time. I just had a to create a gitlab account...

I have an automated mirror of the repo in GitHub for people to use for downloads, discoverability, etc.

Setting up a GitLab account is a super low barrier of entry, as they have many OAuth options for developers to choose from.

If the SSWG were to have a more strict requirement on hosting aside from

> - Link to source (GitHub by default)

I'd be considerate of it and use purely GitHub, but since I am (and as far as I foresee) the primary maintainer responsible for the package - I find a lot of benefits and qualities of life on GitLab that GitHub lacks.

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [June 21, 2019, 11:14pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/8 "2019-06-21T23:14:30Z")

</div>

Thanks for your great and in-depth response!

> [@Mordil](#):
>
> > I'm not in love with the name `RedisCommandContext` because really it's just a command and its response promise. What about `RedisCommand` or `RedisCommandExecution` ? Also please add to the documentation that this type does not have value semantics (it's a `struct` so people might expect that).
> 
> `RedisCommand` sounds about right. Do you think it being a struct still makes sense (as long as it's documented clearly that it's meant to have reference semantics), or should it become a `class` ?

I think it's totally fine as a `struct` but a comment can't hurt 🙂.

> [@Mordil](#):
>
> > can we please get rid of all the `withUnsafe*Bytes` ? Swift is a memory-safe language unless you use `*unsafe*` . Especially network parsers should stay clear of using this because that brings us back straight back to the unsafe C land. If there's a concept concept that you cannot express with `ByteBuffer` 's APIs that don't contain `*unsafe*` , please file a bug. (That is unless you have to interface with C of course where you'll need the `*unsafe*` functions).
> 
> This is probably where a 2nd contributor to do code reviews probably would've been helpful. I might have gotten too "clever" (in my own eyes) without some due diligence into risk / benefits analysis.
> 
> I know of a few places where I can easily replace this, and I'll review other locations as well. [#55](https://gitlab.com/Mordil/swift-redis-nio-client/issues/55) tracks this; please feel free to add to the list that you saw.

Thanks! Added a few, I think this codebase should be free of `withUnsafe` and all the `bindMemory` stuff, if you have a case where you believe that's necessary for performance or to even achieve the functionality, please reach out. SwiftNIO must allow writing such code in a safe way. (Again, for C interop it will always be necessary but we don't need that here.)

> [@Mordil](#):
>
> If you're referring to the issue that was caught by the Vapor/Jobs package, I think that issue would have still come up even with `ByteToMessageVerifier` tests, simply due to a gap in types and over-eager generics.

No, I wasn't aware of that one. I was more thinking about partial messages from the network. And I _think_ it also assumes that the `readerIndex` always starts at `0` (which isn't true). But `ByteToMessageVerifier` at the moment validates about three things:

- can it handle messages that arrive in multiple chunks (for example 1 byte at a time)
- can it handle multiple messages arriving in one big chunk
- can it handle `ByteBuffer`s where the `readerIndex` is `> 0` to start with

and maybe something else I forgot 🙂.

> [@Mordil](#):
>
> Do we want a standalone encoder/decoder that we instantiate, perhaps with some sort of configuration like @tanner0101 suggested - or do we want extensions on `ByteBuffer` ? I could see the benefits of both, or one over the other.

For the individual parse/serialise methods I think some (internal) `ByteBuffer` extensions could work quite well. Something like `readBasicString` and `writeBasicString` but I totally don't mind having that on some special type either, that would be `internal` anyway, no? It just feels easier to extend `ByteBuffer` with that.

Not sure if you were asking about that but regarding the user API I think having separate decoder and encoder `ChannelHandler`s/`ByteToMessageDe/Encoder`s is great. If you're worried about the API of the user having to put in multiple handlers, I think `NIOHTTP1`'s (and also `NIOHTTP2`) model is quite good, there are separate `ChannelHandler`s for encoding, decoding, handling protocol errors, pipelining etc but then there's also a `ChannelPipeline` extension where the user just says [`channel.pipeline.configureHTTPServerPipeline`](https://apple.github.io/swift-nio/docs/current/NIOHTTP1/Extensions/ChannelPipeline.html#/s:3NIO15ChannelPipelineC8NIOHTTP1E019configureHTTPServerC08position24withPipeliningAssistance0H13ServerUpgrade0H13ErrorHandlingAA15EventLoopFutureCyytGAC8PositionO_SbSayAD0F16ProtocolUpgrader_pG9upgraders_yAA0B14HandlerContextCc010completionV0tSgSbtF) which just puts a [bunch of handlers](https://github.com/apple/swift-nio/blob/2.3.0/Sources/NIOHTTP1/HTTPPipelineSetup.swift#L97-L123) into the pipeline.

But in RedisNIO's case, the really "user"-facing API is much easier to use anyway and I would expect that most RedisNIO users will probably use the more high-level API rather than the `ChannelPipeline` one.

> [@Mordil](#):
>
> If the SSWG were to have a more strict requirement on hosting aside from

Gitlab is totally acceptable. I personally believe [github.com](http://github.com) has basically two advantages: 1) lower barrier to contribution because most developers have a [github.com](http://github.com) account anyway and are familiar with the UI 2) given that clashes are a real issue, I reckon people mostly search [github.com](http://github.com) and probably forget other, less popular services. Point 2 is obviously solved by an automatic mirroring solution.

---

<div class="post-metadata">

**Author:** ![tomerd](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tomerd/32/3402_2.png) [@tomerd](https://forums.swift.org/u/tomerd)\
**Post date:** [July 3, 2019, 8:25pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/9 "2019-07-03T20:25:01Z")

</div>

@Mordil given that the client was accepted into sandbox 🥳 should we lock this thread? or further discussion needed?

---

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [July 3, 2019, 10:12pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/10 "2019-07-03T22:12:33Z")

</div>

@tomerd Yes, this thread can now be closed. Any further feedback should be with issues at [Issues · Nathan Harris / RediStack · GitLab](https://gitlab.com/mordil/swift-redis-nio-client/issues)

I have a main issue for all the feedback from this thread at [SSWG Review Feedback (#47) · Issues · Nathan Harris / RediStack · GitLab](https://gitlab.com/mordil/swift-redis-nio-client/issues/47)

---

<div class="post-metadata">

**Author:** ![tomerd](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tomerd/32/3402_2.png) [@tomerd](https://forums.swift.org/u/tomerd)\
**Post date:** [July 5, 2019, 6:11pm UTC](https://forums.swift.org/t/feedback-redisnio-a-nio-based-redis-driver/25521/11 "2019-07-05T18:11:25Z")

</div>


