Skip to content

Commit 9c9d5ea

Browse files
guoye-zhangLukasa
andauthored
Convert WebSocket URLRequest to RFC8441 HTTPRequest (#92)
WebSocket uses the extended-CONNECT form in HTTP/2 and HTTP/3. When creating an HTTPRequest from a URLRequest, we should translate it into the new form. `:method`: `GET` -> `CONNECT` `:scheme`: `wss`/`ws` -> `https`/`http` `:protocol`: `websocket` HTTPTypes defaults to modern HTTP versions, and the expectation is that the HTTP/1 serializer has to translate the extended-CONNECT request back to the old request form. --------- Co-authored-by: Cory Benfield <[email protected]>
1 parent 4366cd1 commit 9c9d5ea

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

Sources/HTTPTypesFoundation/URLRequest+HTTPTypes.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ extension URLRequest {
2525
/// Create a `URLRequest` from an `HTTPRequest`.
2626
/// - Parameter httpRequest: The HTTP request to convert from.
2727
public init?(httpRequest: HTTPRequest) {
28+
// Translate an extended-CONNECT WebSocket request to the legacy form
29+
if httpRequest.method == .connect && httpRequest.extendedConnectProtocol == "websocket" {
30+
var legacyRequest = httpRequest
31+
legacyRequest.method = .get
32+
switch httpRequest.scheme?.lowercased() {
33+
case "https":
34+
legacyRequest.scheme = "wss"
35+
case "http":
36+
legacyRequest.scheme = "ws"
37+
default:
38+
break
39+
}
40+
legacyRequest.extendedConnectProtocol = nil
41+
self.init(httpRequest: legacyRequest)
42+
return
43+
}
44+
2845
guard let url = httpRequest.url else {
2946
return nil
3047
}
@@ -63,6 +80,27 @@ extension URLRequest {
6380
}
6481
}
6582
}
83+
84+
// Translate a legacy WebSocket request to the extended-CONNECT form
85+
if method == .get, let scheme = request.scheme {
86+
switch scheme.utf8.count {
87+
case 3:
88+
if scheme.lowercased() == "wss" {
89+
request.method = .connect
90+
request.scheme = "https"
91+
request.extendedConnectProtocol = "websocket"
92+
}
93+
case 2:
94+
if scheme.lowercased() == "ws" {
95+
request.method = .connect
96+
request.scheme = "http"
97+
request.extendedConnectProtocol = "websocket"
98+
}
99+
default:
100+
break
101+
}
102+
}
103+
66104
return request
67105
}
68106
}

Tests/HTTPTypesFoundationTests/HTTPTypesFoundationTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,22 @@ final class HTTPTypesFoundationTests: XCTestCase {
143143
XCTAssertEqual(request.headerFields[.init("x-foo")!], "Bar")
144144
}
145145

146+
func testWebSocketRequest() throws {
147+
let urlRequest = URLRequest(url: URL(string: "wss://www.example.com/")!)
148+
149+
let request = try XCTUnwrap(urlRequest.httpRequest)
150+
XCTAssertEqual(request.method, .connect)
151+
XCTAssertEqual(request.scheme, "https")
152+
XCTAssertEqual(request.authority, "www.example.com")
153+
XCTAssertEqual(request.path, "/")
154+
XCTAssertEqual(request.extendedConnectProtocol, "websocket")
155+
156+
let urlRequestConverted = try XCTUnwrap(URLRequest(httpRequest: request))
157+
XCTAssertEqual(urlRequestConverted.httpMethod, "GET")
158+
XCTAssertEqual(urlRequestConverted.url, URL(string: "wss://www.example.com/"))
159+
XCTAssertEqual(urlRequest, urlRequestConverted)
160+
}
161+
146162
func testResponseToFoundation() throws {
147163
let response = HTTPResponse(
148164
status: .ok,

0 commit comments

Comments
 (0)