Skip to content
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

WebSocket Router #41

Merged
merged 7 commits into from Mar 20, 2024
Merged

WebSocket Router #41

merged 7 commits into from Mar 20, 2024

Conversation

adam-fowler
Copy link
Member

@adam-fowler adam-fowler commented Mar 15, 2024

Initialise websocket upgrade with a router. This can be the same router as the one you use for everything else or a separate router, which would be more optimal given you are testing less routes for the upgrade.

let router = Router(context: BasicWebSocketRequestContext.self)
router.ws("/ws") { request,channel in
    return .upgrade([:])
} handle: { inbound, outbound, _ in
    for try await packet in inbound {
        if case .text("disconnect") = packet {
            break
        }
        try await outbound.write(.custom(packet.webSocketFrame))
    }
}

let app = Application(
    router: router,
    server: .webSocketUpgrade(webSocketRouter: router)
)
try await app.runService()

This PR also removes any NIOHTTP1 types from the public interface

Tests/HummingbirdWebSocketTests/WebSocketTests.swift Outdated Show resolved Hide resolved
}

func testRouteSelectionFail() async throws {
try XCTSkipIf(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we skipping this test? Shouldn't we still verify that websockets are not always upgraded?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a temporary thing should have mentioned this. The tests hangs and I havent worked out why this is happening

Comment on lines +171 to +175
let logger = {
var logger = Logger(label: "WebSocketTest")
logger.logLevel = .debug
return logger
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let logger = {
var logger = Logger(label: "WebSocketTest")
logger.logLevel = .debug
return logger
}()
var logger = Logger(label: "WebSocketTest")
logger.logLevel = .debug

Copy link
Member Author

Choose a reason for hiding this comment

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

That's means we have a mutable logger, which we cannot use in a child task

/// - parameters
public static func webSocketUpgrade<ResponderBuilder: HTTPResponderBuilder>(
additionalChannelHandlers: @autoclosure @escaping @Sendable () -> [any RemovableChannelHandler] = [],
maxFrameSize: Int = 1 << 14,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make this a parameter configuration: WebSocketServerConfiguration?
maxFrameSize is definitely the most important configurable property, but I can imagine us adding a few more in the future.

public init<Context: WebSocketRequestContext, ResponderBuilder: HTTPResponderBuilder>(
additionalChannelHandlers: @escaping @Sendable () -> [any RemovableChannelHandler] = { [] },
responder: @escaping @Sendable (Request, Channel) async throws -> Response = { _, _ in throw HTTPError(.notImplemented) },
maxFrameSize: Int = (1 << 14),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make this a parameter configuration: WebSocketServerConfiguration?

@@ -19,16 +19,16 @@ import NIOCore
public protocol WebSocketContextProtocol: Sendable {
var logger: Logger { get }
var allocator: ByteBufferAllocator { get }
init(logger: Logger, allocator: ByteBufferAllocator)
init(channel: Channel, logger: Logger)
}

/// Default implementation of ``WebSocketContextProtocol``
public struct WebSocketContext: WebSocketContextProtocol {
public let logger: Logger
public let allocator: ByteBufferAllocator
Copy link
Contributor

Choose a reason for hiding this comment

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

I can imagine that the remote address is useful

Copy link
Contributor

Choose a reason for hiding this comment

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

Likewise the original HTTPRequest could be useful in a WebSocketContextProtocol?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is basically a duplicate of the requirements from the RequestContext so including the request might be hard. You get the request in the shouldUpgrade call so things like permessage-deflate can be negotiated there

@adam-fowler
Copy link
Member Author

Fixed issues with WebSocket upgrade fail not sending response. Solution isn't great, hopefully will get better solution from NIO at some point.

Comment on lines +188 to +210
package struct RequestID: CustomStringConvertible {
let low: UInt64

package init() {
self.low = Self.globalRequestID.loadThenWrappingIncrement(by: 1, ordering: .relaxed)
}

package var description: String {
Self.high + self.formatAsHexWithLeadingZeros(self.low)
}

func formatAsHexWithLeadingZeros(_ value: UInt64) -> String {
let string = String(value, radix: 16)
if string.count < 16 {
return String(repeating: "0", count: 16 - string.count) + string
} else {
return string
}
}

private static let high = String(UInt64.random(in: .min ... .max), radix: 16)
private static let globalRequestID = ManagedAtomic<UInt64>(UInt64.random(in: .min ... .max))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be removed now

Copy link
Contributor

@Joannis Joannis left a comment

Choose a reason for hiding this comment

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

Looks good to me, I think we should still do a full API pass

@adam-fowler adam-fowler merged commit 60f1f10 into main Mar 20, 2024
3 of 4 checks passed
@adam-fowler adam-fowler deleted the websocket-router branch March 20, 2024 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants