mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-05-03 07:32:29 +00:00
4316ecae09
### 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.
127 lines
4.2 KiB
Swift
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
|
|
}
|
|
}
|