Files
async-http-client/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift
T
aryan-25 4316ecae09 Add support for request body to be larger than 2GB on 32-bit devices (#746)
### Motivation:

- The properties that store the request body length and the cumulative number of bytes sent as part of a request are of type `Int`. 
- On 32-bit devices, when sending requests larger than `Int32.max`, these properties overflow and cause a crash.
- To solve this problem, the properties should use the explicit `Int64` type.

### Modifications:

- Changed the type of the `known` field of the `RequestBodyLength` enum to `Int64`.
- Changed the type of `expectedBodyLength` and `sentBodyBytes` in `HTTPRequestStateMachine` to `Int64?` and `Int64` respectively.
- Deprecated the `public var length: Int?` property of `HTTPClient.Body` and backed it with a new property: `contentLength: Int64?`
  - Added a new initializer and "overloaded" the `stream` function in `HTTPClient.Body` to work with the new `contentLength` property.
  - **Note:** The newly added `stream` function has different parameter names (`length` -> `contentLength` and `stream` -> `bodyStream`) to avoid ambiguity problems.
- Added a test case that streams a 3GB request -- verified this fails with the types of the properties set explicitly to `Int32`. 

### Result:

- 32-bit devices can send requests larger than 2GB without integer overflow issues.
2024-06-28 11:33:04 +02:00

127 lines
4.2 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import struct Foundation.URL
import NIOCore
import NIOHTTP1
import NIOSSL
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClientRequest {
struct Prepared {
enum Body {
case asyncSequence(
length: RequestBodyLength,
nextBodyPart: (ByteBufferAllocator) async throws -> ByteBuffer?
)
case sequence(
length: RequestBodyLength,
canBeConsumedMultipleTimes: Bool,
makeCompleteBody: (ByteBufferAllocator) -> ByteBuffer
)
case byteBuffer(ByteBuffer)
}
var url: URL
var poolKey: ConnectionPool.Key
var requestFramingMetadata: RequestFramingMetadata
var head: HTTPRequestHead
var body: Body?
var tlsConfiguration: TLSConfiguration?
}
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClientRequest.Prepared {
init(_ request: HTTPClientRequest, dnsOverride: [String: String] = [:]) throws {
guard let url = URL(string: request.url) else {
throw HTTPClientError.invalidURL
}
let deconstructedURL = try DeconstructedURL(url: url)
var headers = request.headers
headers.addHostIfNeeded(for: deconstructedURL)
let metadata = try headers.validateAndSetTransportFraming(
method: request.method,
bodyLength: .init(request.body)
)
self.init(
url: url,
poolKey: .init(url: deconstructedURL, tlsConfiguration: request.tlsConfiguration, dnsOverride: dnsOverride),
requestFramingMetadata: metadata,
head: .init(
version: .http1_1,
method: request.method,
uri: deconstructedURL.uri,
headers: headers
),
body: request.body.map { .init($0) },
tlsConfiguration: request.tlsConfiguration
)
}
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClientRequest.Prepared.Body {
init(_ body: HTTPClientRequest.Body) {
switch body.mode {
case .asyncSequence(let length, let makeAsyncIterator):
self = .asyncSequence(length: length, nextBodyPart: makeAsyncIterator())
case .sequence(let length, let canBeConsumedMultipleTimes, let makeCompleteBody):
self = .sequence(length: length, canBeConsumedMultipleTimes: canBeConsumedMultipleTimes, makeCompleteBody: makeCompleteBody)
case .byteBuffer(let byteBuffer):
self = .byteBuffer(byteBuffer)
}
}
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension RequestBodyLength {
init(_ body: HTTPClientRequest.Body?) {
switch body?.mode {
case .none:
self = .known(0)
case .byteBuffer(let buffer):
self = .known(Int64(buffer.readableBytes))
case .sequence(let length, _, _), .asyncSequence(let length, _):
self = length
}
}
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClientRequest {
func followingRedirect(
from originalURL: URL,
to redirectURL: URL,
status: HTTPResponseStatus
) -> HTTPClientRequest {
let (method, headers, body) = transformRequestForRedirect(
from: originalURL,
method: self.method,
headers: self.headers,
body: self.body,
to: redirectURL,
status: status
)
var newRequest = HTTPClientRequest(url: redirectURL.absoluteString)
newRequest.method = method
newRequest.headers = headers
newRequest.body = body
return newRequest
}
}