-
Notifications
You must be signed in to change notification settings - Fork 656
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add a WebSocket client upgrader. #1038
Merged
Merged
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
cef818d
Add a WebSocket client upgrader.
476e6b0
Neatened web socket and client upgrader tests.
c1e2f8c
Merge branch 'master' into websocket-client-upgrader
tigerpixel d658e9a
Added comments to the WebSocket client upgrader. Neatened with client…
081582e
Merge branch 'websocket-client-upgrader' of https://github.com/tigerp…
623b2ed
Change variables to constants in WebSocket headers.
775959b
Merge branch 'master' into websocket-client-upgrader
tigerpixel 46787e0
Merge branch 'master' into websocket-client-upgrader
weissi 35606b9
Merge branch 'master' into websocket-client-upgrader
tigerpixel 87d43e3
Merge branch 'master' into websocket-client-upgrader
Lukasa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftNIO open source project | ||
// | ||
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import NIO | ||
import NIOHTTP1 | ||
|
||
/// A `HTTPClientProtocolUpgrader` that knows how to do the WebSocket upgrade dance. | ||
/// | ||
/// This upgrader assumes that the `HTTPClientUpgradeHandler` will create and send the upgrade request. | ||
/// This upgrader also assumes that the `HTTPClientUpgradeHandler` will appropriately mutate the | ||
/// pipeline to remove the HTTP `ChannelHandler`s. | ||
public final class NIOWebClientSocketUpgrader: NIOHTTPClientProtocolUpgrader { | ||
|
||
/// RFC 6455 specs this as the required entry in the Upgrade header. | ||
public var supportedProtocol: String = "websocket" | ||
tigerpixel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// None of the websocket headers are actually defined as 'required'. | ||
public var requiredUpgradeHeaders: [String] = [] | ||
|
||
private let requestKey: String | ||
private let maxFrameSize: Int | ||
private let automaticErrorHandling: Bool | ||
private let upgradePipelineHandler: (Channel, HTTPResponseHead) -> EventLoopFuture<Void> | ||
|
||
public init(requestKey: String, | ||
maxFrameSize: Int = 1 << 14, | ||
automaticErrorHandling: Bool = true, | ||
upgradePipelineHandler: @escaping (Channel, HTTPResponseHead) -> EventLoopFuture<Void>) { | ||
|
||
precondition(requestKey != "", "The request key must contain a valid Sec-WebSocket-Key") | ||
precondition(maxFrameSize <= UInt32.max, "invalid overlarge max frame size") | ||
self.requestKey = requestKey | ||
self.upgradePipelineHandler = upgradePipelineHandler | ||
self.maxFrameSize = maxFrameSize | ||
self.automaticErrorHandling = automaticErrorHandling | ||
} | ||
|
||
public func addCustom(upgradeRequestHeaders: inout HTTPHeaders) { | ||
Lukasa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
upgradeRequestHeaders.add(name: "Sec-WebSocket-Key", value: self.requestKey) | ||
upgradeRequestHeaders.add(name: "Sec-WebSocket-Version", value: "13") | ||
} | ||
|
||
public func shouldAllowUpgrade(upgradeResponse: HTTPResponseHead) -> Bool { | ||
Lukasa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let acceptValueHeader = upgradeResponse.headers["Sec-WebSocket-Accept"] | ||
|
||
guard acceptValueHeader.count == 1 else { | ||
return false | ||
} | ||
|
||
// Validate the response key in 'Sec-WebSocket-Accept'. | ||
let expectedAcceptValue: String | ||
do { | ||
tigerpixel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var hasher = SHA1() | ||
hasher.update(string: self.requestKey) | ||
hasher.update(string: magicWebSocketGUID) | ||
expectedAcceptValue = String(base64Encoding: hasher.finish()) | ||
} | ||
|
||
return expectedAcceptValue == acceptValueHeader[0] | ||
} | ||
|
||
public func upgrade(context: ChannelHandlerContext, upgradeResponse: HTTPResponseHead) -> EventLoopFuture<Void> { | ||
|
||
var upgradeFuture = context.pipeline.addHandler(WebSocketFrameEncoder()).flatMap {_ in | ||
tigerpixel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
context.pipeline.addHandler(ByteToMessageHandler(WebSocketFrameDecoder(maxFrameSize: self.maxFrameSize))) | ||
} | ||
|
||
if self.automaticErrorHandling { | ||
upgradeFuture = upgradeFuture.flatMap { context.pipeline.addHandler(WebSocketProtocolErrorHandler()) | ||
tigerpixel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
return upgradeFuture.flatMap { _ in | ||
self.upgradePipelineHandler(context.channel, upgradeResponse) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
Tests/NIOWebSocketTests/WebSocketClientEndToEndTests+XCTest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftNIO open source project | ||
// | ||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// WebSocketClientEndToEndTests+XCTest.swift | ||
// | ||
import XCTest | ||
|
||
/// | ||
/// NOTE: This file was generated by generate_linux_tests.rb | ||
/// | ||
/// Do NOT edit this file directly as it will be regenerated automatically when needed. | ||
/// | ||
|
||
extension WebSocketClientEndToEndTests { | ||
|
||
static var allTests : [(String, (WebSocketClientEndToEndTests) -> () throws -> Void)] { | ||
return [ | ||
("testSimpleUpgradeSucceeds", testSimpleUpgradeSucceeds), | ||
("testRejectUpgradeIfMissingAcceptKey", testRejectUpgradeIfMissingAcceptKey), | ||
("testRejectUpgradeIfIncorrectAcceptKey", testRejectUpgradeIfIncorrectAcceptKey), | ||
("testRejectUpgradeIfNotWebsocket", testRejectUpgradeIfNotWebsocket), | ||
("testSendAFewFrames", testSendAFewFrames), | ||
("testReceiveAFewFrames", testReceiveAFewFrames), | ||
] | ||
} | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a typo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tanner0101 omg, yes, this is a typo :|. Mind making PR to rename (alongside a deprecated
typealias
to have theNIOWebClientSocketUpgrader
still valid to not break SemVer).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tanner0101 @weissi Sorry, my bad. Definitely is a typo. I am not sure how that happened. Changing this will also require a change to the WebSocketClientDemo to use the updated class name rather than the old via the type alias.
I am happy to make a PR for one or both of these if you would rather not do it. Let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tigerpixel don't apologise, we all missed it :). We can land these changes in either 1 or 2 PRs, just need to make sure we retain a (deprecated) typealias so the typo'd name stays usable (for SemVer compatibility). NIO's public API breakage checker should however catch any mistakes that we might make.