Skip to content

Commit 30b2af2

Browse files
authored
Feat: Bind http2 (#132)
1 parent 105f632 commit 30b2af2

36 files changed

+1497
-161
lines changed

Source/AwsCommonRuntimeKit/auth/signing/Signer.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public class Signer {
3030
/// - `Throws`: An error of type `AwsCommonRuntimeError` which will pull last error found in the CRT
3131
/// - `Returns`: Returns a signed http request `HttpRequest`
3232
public static func signRequest(
33-
request: HTTPRequest,
33+
request: HTTPRequestBase,
3434
config: SigningConfig,
35-
allocator: Allocator = defaultAllocator) async throws -> HTTPRequest {
35+
allocator: Allocator = defaultAllocator) async throws -> HTTPRequestBase {
3636

3737
guard let signable = aws_signable_new_http_request(allocator.rawValue, request.rawValue) else {
3838
throw CommonRunTimeError.crtError(.makeFromLastError())
@@ -41,7 +41,9 @@ public class Signer {
4141
aws_signable_destroy(signable)
4242
}
4343

44-
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<HTTPRequest, Error>) in
44+
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
45+
HTTPRequestBase,
46+
Error>) in
4547
let signRequestCore = SignRequestCore(request: request,
4648
continuation: continuation,
4749
shouldSignHeader: config.shouldSignHeader,
@@ -74,11 +76,11 @@ public class Signer {
7476

7577
class SignRequestCore {
7678
let allocator: Allocator
77-
let request: HTTPRequest
78-
var continuation: CheckedContinuation<HTTPRequest, Error>
79+
let request: HTTPRequestBase
80+
var continuation: CheckedContinuation<HTTPRequestBase, Error>
7981
let shouldSignHeader: ((String) -> Bool)?
80-
init(request: HTTPRequest,
81-
continuation: CheckedContinuation<HTTPRequest, Error>,
82+
init(request: HTTPRequestBase,
83+
continuation: CheckedContinuation<HTTPRequestBase, Error>,
8284
shouldSignHeader: ((String) -> Bool)? = nil,
8385
allocator: Allocator) {
8486
self.allocator = allocator

Source/AwsCommonRuntimeKit/crt/Allocator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public final class TracingAllocator: Allocator {
7171
* - Parameter framesPerStack: How many frames to record for each allocation
7272
* (8 as usually a good default to start with).
7373
*/
74-
public convenience init(tracingStacksOf allocator: Allocator, framesPerStack: Int = 16) {
74+
public convenience init(tracingStacksOf allocator: Allocator, framesPerStack: Int = 10) {
7575
self.init(allocator, level: .stacks, framesPerStack: framesPerStack)
7676
}
7777

Source/AwsCommonRuntimeKit/crt/CStruct.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,47 @@ func withOptionalCStructPointer<Arg1Type: CStruct,
106106
return withOptionalCStructPointer(arg1, arg2) { arg1Pointer, arg2Pointer in
107107
return withOptionalCStructPointer(arg3, arg4) { arg3Pointer, arg4Pointer in
108108
return withOptionalCStructPointer(to: arg5) { arg5Pointer in
109-
return body(arg1Pointer, arg2Pointer, arg3Pointer, arg4Pointer, arg5Pointer)
109+
return body(
110+
arg1Pointer,
111+
arg2Pointer,
112+
arg3Pointer,
113+
arg4Pointer,
114+
arg5Pointer)
115+
}
116+
}
117+
}
118+
}
119+
120+
func withOptionalCStructPointer<Arg1Type: CStruct,
121+
Arg2Type: CStruct,
122+
Arg3Type: CStruct,
123+
Arg4Type: CStruct,
124+
Arg5Type: CStruct,
125+
Arg6Type: CStruct,
126+
Result>(
127+
_ arg1: Arg1Type?,
128+
_ arg2: Arg2Type?,
129+
_ arg3: Arg3Type?,
130+
_ arg4: Arg4Type?,
131+
_ arg5: Arg5Type?,
132+
_ arg6: Arg6Type?,
133+
_ body: (UnsafePointer<Arg1Type.RawType>?,
134+
UnsafePointer<Arg2Type.RawType>?,
135+
UnsafePointer<Arg3Type.RawType>?,
136+
UnsafePointer<Arg4Type.RawType>?,
137+
UnsafePointer<Arg5Type.RawType>?,
138+
UnsafePointer<Arg6Type.RawType>?) -> Result
139+
) -> Result {
140+
return withOptionalCStructPointer(arg1, arg2) { arg1Pointer, arg2Pointer in
141+
return withOptionalCStructPointer(arg3, arg4) { arg3Pointer, arg4Pointer in
142+
return withOptionalCStructPointer(arg5, arg6) { arg5Pointer, arg6Pointer in
143+
return body(
144+
arg1Pointer,
145+
arg2Pointer,
146+
arg3Pointer,
147+
arg4Pointer,
148+
arg5Pointer,
149+
arg6Pointer)
110150
}
111151
}
112152
}

Source/AwsCommonRuntimeKit/crt/Utilities.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,25 @@ extension Data {
6666
}
6767
}
6868

69+
func withAWSByteCursorPointer<Result>(_ body: (UnsafeMutablePointer<aws_byte_cursor>) -> Result) -> Result {
70+
let count = self.count
71+
return self.withUnsafeBytes { rawBufferPointer -> Result in
72+
var cursor = aws_byte_cursor_from_array(rawBufferPointer.baseAddress, count)
73+
return withUnsafeMutablePointer(to: &cursor) {
74+
body($0)
75+
}
76+
}
77+
}
78+
6979
public func encodeToHexString() -> String {
7080
map { String(format: "%02x", $0) }.joined()
7181
}
82+
83+
func chunked(into size: Int) -> [Data] {
84+
return stride(from: 0, to: count, by: size).map {
85+
self[$0 ..< Swift.min($0 + size, count)]
86+
}
87+
}
7288
}
7389

7490
extension aws_date_time {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0.
3+
import AwsCHttp
4+
import Foundation
5+
6+
/// An HTTP1Stream represents a single HTTP/1.1 specific Http Request/Response.
7+
public class HTTP1Stream: HTTPStream {
8+
/// Stream keeps a reference to HttpConnection to keep it alive
9+
private let httpConnection: HTTPClientConnection
10+
11+
// Called by HTTPClientConnection
12+
init(
13+
httpConnection: HTTPClientConnection,
14+
options: aws_http_make_request_options,
15+
callbackData: HTTPStreamCallbackCore) throws {
16+
guard let rawValue = withUnsafePointer(
17+
to: options, { aws_http_connection_make_request(httpConnection.rawValue, $0) }) else {
18+
throw CommonRunTimeError.crtError(.makeFromLastError())
19+
}
20+
self.httpConnection = httpConnection
21+
super.init(rawValue: rawValue, callbackData: callbackData)
22+
}
23+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0.
3+
4+
import AwsCHttp
5+
import AwsCIo
6+
import Foundation
7+
8+
public class HTTP2ClientConnection: HTTPClientConnection {
9+
10+
/// Creates a new http2 stream from the `HTTPRequestOptions` given.
11+
/// - Parameter requestOptions: An `HTTPRequestOptions` struct containing callbacks on
12+
/// the different events from the stream
13+
/// - Returns: An `HTTP2Stream`
14+
override public func makeRequest(requestOptions: HTTPRequestOptions) throws -> HTTPStream {
15+
let httpStreamCallbackCore = HTTPStreamCallbackCore(requestOptions: requestOptions)
16+
do {
17+
return try HTTP2Stream(httpConnection: self,
18+
options: httpStreamCallbackCore.getRetainedHttpMakeRequestOptions(),
19+
callbackData: httpStreamCallbackCore)
20+
} catch {
21+
httpStreamCallbackCore.release()
22+
throw error
23+
}
24+
}
25+
26+
/// Send a SETTINGS frame (HTTP/2 only).
27+
/// SETTINGS will be applied locally when settings ACK is received from peer.
28+
/// - Parameter setting: The settings to change
29+
public func updateSetting(setting: HTTP2Settings) async throws {
30+
try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<(), Error>) in
31+
let continuationCore = ContinuationCore(continuation: continuation)
32+
setting.withCStruct { settingList in
33+
let count = settingList.count
34+
settingList.withUnsafeBufferPointer { pointer in
35+
guard aws_http2_connection_change_settings(
36+
rawValue,
37+
pointer.baseAddress!,
38+
count,
39+
onChangeSettingsComplete,
40+
continuationCore.passRetained()) == AWS_OP_SUCCESS else {
41+
continuationCore.release()
42+
continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
43+
return
44+
}
45+
}
46+
}
47+
})
48+
}
49+
50+
/// Send a PING frame. Round-trip-time is calculated when PING ACK is received from peer.
51+
/// - Parameter data: (Optional) 8 Bytes data with the PING frame. Data count must be exact 8 bytes.
52+
/// - Returns: The round trip time in nanoseconds for the connection.
53+
public func sendPing(data: Data = Data()) async throws -> UInt64 {
54+
try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<UInt64, Error>) in
55+
let continuationCore = ContinuationCore(continuation: continuation)
56+
data.withAWSByteCursorPointer { dataPointer in
57+
guard aws_http2_connection_ping(
58+
rawValue,
59+
data.isEmpty ? nil : dataPointer,
60+
onPingComplete,
61+
continuationCore.passRetained()) == AWS_OP_SUCCESS
62+
else {
63+
continuationCore.release()
64+
continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
65+
return
66+
}
67+
}
68+
})
69+
}
70+
71+
/// Send a custom GOAWAY frame.
72+
///
73+
/// Note that the connection automatically attempts to send a GOAWAY during
74+
/// shutdown (unless a GOAWAY with a valid Last-Stream-ID has already been sent).
75+
///
76+
/// This call can be used to gracefully warn the peer of an impending shutdown
77+
/// (error=0, allowMoreStreams=true), or to customize the final GOAWAY
78+
/// frame that is sent by this connection.
79+
///
80+
/// The other end may not receive the goaway, if the connection already closed.
81+
///
82+
/// - Parameters:
83+
/// - error: The HTTP/2 error code to send.
84+
/// - allowMoreStreams: If true, new peer-initiated streams will continue to be acknowledged and the GOAWAY's Last-Stream-ID will
85+
/// be set to a max value. If false, new peer-initiated streams will be ignored and the GOAWAY's
86+
/// Last-Stream-ID will be set to the latest acknowledged stream.
87+
/// - debugData: (Optional) debug data to send. Size must not exceed 16KB.
88+
public func sendGoAway(error: HTTP2Error, allowMoreStreams: Bool, debugData: Data = Data()) {
89+
debugData.withAWSByteCursorPointer { dataPointer in
90+
aws_http2_connection_send_goaway(
91+
rawValue,
92+
error.rawValue,
93+
allowMoreStreams,
94+
dataPointer)
95+
}
96+
}
97+
}
98+
99+
private func onChangeSettingsComplete(connection: UnsafeMutablePointer<aws_http_connection>?,
100+
errorCode: Int32,
101+
userData: UnsafeMutableRawPointer!) {
102+
let continuation = Unmanaged<ContinuationCore<()>>.fromOpaque(userData).takeRetainedValue().continuation
103+
104+
guard errorCode == AWS_OP_SUCCESS else {
105+
continuation.resume(throwing: CommonRunTimeError.crtError(CRTError(code: errorCode)))
106+
return
107+
}
108+
109+
// SUCCESS
110+
continuation.resume()
111+
}
112+
113+
private func onPingComplete(connection: UnsafeMutablePointer<aws_http_connection>?,
114+
roundTripTimeNs: UInt64,
115+
errorCode: Int32,
116+
userData: UnsafeMutableRawPointer!) {
117+
let continuation = Unmanaged<ContinuationCore<UInt64>>.fromOpaque(userData).takeRetainedValue().continuation
118+
119+
guard errorCode == AWS_OP_SUCCESS else {
120+
continuation.resume(throwing: CommonRunTimeError.crtError(CRTError(code: errorCode)))
121+
return
122+
}
123+
124+
// SUCCESS
125+
continuation.resume(returning: roundTripTimeNs)
126+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0.
3+
4+
/// Error codes that may be present in HTTP/2 RST_STREAM and GOAWAY frames (RFC-7540 7).
5+
public enum HTTP2Error: UInt32 {
6+
case protocolError = 1
7+
case internalError = 2
8+
case flowControlError = 3
9+
case settingsTimeout = 4
10+
case streamClosed = 5
11+
case frameSizeError = 6
12+
case refusedStream = 7
13+
case cancel = 8
14+
case compressionError = 9
15+
case connectError = 10
16+
case enhanceYourCalm = 11
17+
case inadequateSecurity = 12
18+
case HTTP_1_1_Required = 13
19+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0.
3+
4+
import AwsCHttp
5+
6+
/// Predefined settings identifiers (RFC-7540 6.5.2)
7+
/// Nil means use default values
8+
public struct HTTP2Settings: CStruct {
9+
public var headerTableSize: UInt32?
10+
public var enablePush: Bool?
11+
public var maxConcurrentStreams: UInt32?
12+
public var initialWindowSize: UInt32?
13+
public var maxFrameSize: UInt32?
14+
public var maxHeaderListSize: UInt32?
15+
16+
public init(headerTableSize: UInt32? = nil,
17+
enablePush: Bool? = nil,
18+
maxConcurrentStreams: UInt32? = nil,
19+
initialWindowSize: UInt32? = nil,
20+
maxFrameSize: UInt32? = nil,
21+
maxHeaderListSize: UInt32? = nil) {
22+
self.headerTableSize = headerTableSize
23+
self.enablePush = enablePush
24+
self.maxConcurrentStreams = maxConcurrentStreams
25+
self.initialWindowSize = initialWindowSize
26+
self.maxFrameSize = maxFrameSize
27+
self.maxHeaderListSize = maxHeaderListSize
28+
}
29+
30+
typealias RawType = [aws_http2_setting]
31+
func withCStruct<Result>(_ body: ([aws_http2_setting]) -> Result
32+
) -> Result {
33+
var http2SettingList = [aws_http2_setting]()
34+
if let value = headerTableSize {
35+
http2SettingList.append(
36+
aws_http2_setting(
37+
id: AWS_HTTP2_SETTINGS_HEADER_TABLE_SIZE,
38+
value: value))
39+
}
40+
if let value = enablePush {
41+
http2SettingList.append(
42+
aws_http2_setting(
43+
id: AWS_HTTP2_SETTINGS_ENABLE_PUSH,
44+
value: value.uintValue))
45+
}
46+
if let value = maxConcurrentStreams {
47+
http2SettingList.append(
48+
aws_http2_setting(
49+
id: AWS_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
50+
value: value))
51+
}
52+
if let value = initialWindowSize {
53+
http2SettingList.append(
54+
aws_http2_setting(
55+
id: AWS_HTTP2_SETTINGS_INITIAL_WINDOW_SIZE,
56+
value: value))
57+
}
58+
if let value = maxFrameSize {
59+
http2SettingList.append(
60+
aws_http2_setting(
61+
id: AWS_HTTP2_SETTINGS_MAX_FRAME_SIZE,
62+
value: value))
63+
}
64+
if let value = maxHeaderListSize {
65+
http2SettingList.append(
66+
aws_http2_setting(
67+
id: AWS_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
68+
value: value))
69+
}
70+
return body(http2SettingList)
71+
}
72+
}

0 commit comments

Comments
 (0)