Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ let request = HTTPRequest(
path: "/users"
)

try await httpClient.perform(
try await HTTP.perform(
request: request,
body: nil
) { response, responseBodyAndTrailers in
print("Status: \(response.status)")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift HTTP API Proposal open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift HTTP API Proposal project authors
// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand All @@ -12,9 +12,5 @@
//
//===----------------------------------------------------------------------===//

@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
@usableFromInline
struct DefaultHTTPClientEventHandler: HTTPClientEventHandler, ~Copyable {
@inlinable
init() {}
}
/// The namespace for HTTP.
public enum HTTP {}
40 changes: 40 additions & 0 deletions Sources/HTTPAPIs/Client/HTTPClient+Conveniences.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift HTTP API Proposal open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift HTTP API Proposal project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
extension HTTPClient {
/// Performs an HTTP request and processes the response.
///
/// This convenience method provides default values for `body` and `options` arguments,
/// making it easier to execute HTTP requests without specifying optional parameters.
///
/// - Parameters:
/// - request: The HTTP request header to send.
/// - body: The optional request body to send. Defaults to no body.
/// - options: The options for this request. Defaults to an empty initialized `RequestOptions`.
/// - responseHandler: The closure to process the response. This closure is invoked
/// when the response header is received and can read the response body.
///
/// - Returns: The value returned by the response handler closure.
///
/// - Throws: An error if the request fails or if the response handler throws.
public func perform<Return: ~Copyable>(
request: HTTPRequest,
body: consuming HTTPClientRequestBody<RequestWriter>? = nil,
options: RequestOptions = .init(),
responseHandler: (HTTPResponse, consuming ResponseConcludingReader) async throws -> Return,
) async throws -> Return {
return try await self.perform(request: request, body: body, options: options, responseHandler: responseHandler)
}
}
138 changes: 14 additions & 124 deletions Sources/HTTPAPIs/Client/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,18 @@
@_exported public import AsyncStreaming
@_exported public import HTTPTypes

#if canImport(Darwin)
public import Security
#endif

/// A protocol that defines the interface for an HTTP client.
///
/// ``HTTPClient`` provides asynchronous request execution with streaming request
/// and response bodies.
@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
public protocol HTTPClient<RequestConcludingWriter, ResponseConcludingReader>: ~Copyable {
public protocol HTTPClient<RequestOptions>: ~Copyable {
associatedtype RequestOptions: HTTPClientCapability.RequestOptions

/// The type used to write request body data and trailers.
// TODO: Check if we should allow ~Escapable readers https://github.com/apple/swift-http-api-proposal/issues/13
associatedtype RequestConcludingWriter: ConcludingAsyncWriter, ~Copyable, SendableMetatype
where RequestConcludingWriter.Underlying.WriteElement == UInt8, RequestConcludingWriter.FinalElement == HTTPFields?
associatedtype RequestWriter: AsyncWriter, ~Copyable, SendableMetatype
where RequestWriter.WriteElement == UInt8

/// The type used to read response body data and trailers.
// TODO: Check if we should allow ~Escapable writers https://github.com/apple/swift-http-api-proposal/issues/13
Expand All @@ -39,132 +37,24 @@ public protocol HTTPClient<RequestConcludingWriter, ResponseConcludingReader>: ~

/// Performs an HTTP request and processes the response.
///
/// This method executes the HTTP request with the specified configuration and event
/// handler, then invokes the response handler when the response headers are received.
/// The request and response bodies are streamed using the client's writer and reader types.
/// This method executes the HTTP request with the specified options, then invokes
/// the response handler when the response header is received. The request and
/// response bodies are streamed using the client's writer and reader types.
///
/// - Parameters:
/// - request: The HTTP request headers to send.
/// - request: The HTTP request header to send.
/// - body: The optional request body to send. When `nil`, no body is sent.
/// - configuration: The configuration settings for this request.
/// - eventHandler: The handler for processing events during request execution.
/// - options: The options for this request.
/// - responseHandler: The closure to process the response. This closure is invoked
/// when the response headers are received and can read the response body.
/// when the response header is received and can read the response body.
///
/// - Returns: The value returned by the response handler closure.
///
/// - Throws: An error if the request fails or if the response handler throws.
func perform<Return>(
func perform<Return: ~Copyable>(
request: HTTPRequest,
body: consuming HTTPClientRequestBody<RequestConcludingWriter>?,
configuration: HTTPClientConfiguration,
eventHandler: borrowing some HTTPClientEventHandler & ~Escapable & ~Copyable,
body: consuming HTTPClientRequestBody<RequestWriter>?,
options: RequestOptions,
responseHandler: (HTTPResponse, consuming ResponseConcludingReader) async throws -> Return
) async throws -> Return
}

@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
extension HTTPClient {
#if canImport(Darwin)
/// Performs an HTTP request and processes the response.
///
/// This method executes the HTTP request with the specified configuration and event
/// handler, then invokes the response handler when the response headers are received.
/// The request and response bodies are streamed using the client's writer and reader types.
///
/// - Parameters:
/// - request: The HTTP request headers to send.
/// - body: The optional request body to send. When `nil`, no body is sent.
/// - configuration: The configuration settings for this request.
/// - eventHandler: The handler for processing events during request execution.
/// - responseHandler: The closure to process the response. This closure is invoked
/// when the response headers are received and can read the response body.
/// - onRedirection: The redirection handler overriding the one on `eventHandler`.
/// - onServerTrust: The server trust handler overriding the one on `eventHandler`.
///
/// - Returns: The value returned by the response handler closure.
///
/// - Throws: An error if the request fails or if the response handler throws.
@_alwaysEmitIntoClient
public func perform<Return>(
request: HTTPRequest,
body: consuming HTTPClientRequestBody<RequestConcludingWriter>? = nil,
configuration: HTTPClientConfiguration = .init(),
eventHandler: consuming some HTTPClientEventHandler & ~Escapable & ~Copyable =
DefaultHTTPClientEventHandler(),
responseHandler: (HTTPResponse, consuming ResponseConcludingReader) async throws -> Return,
onRedirection: (
(_ response: HTTPResponse, _ newRequest: HTTPRequest) async throws ->
HTTPClientRedirectionAction
)? = nil,
onServerTrust: ((_ trust: SecTrust) async throws -> HTTPClientTrustResult)? = nil,
) async throws -> Return {
// Since the element is ~Copyable but we don't have call-once closures
// we need to move it into an Optional and then take it out once
var consumedBody = consume body
return try await ScopedHTTPClientEventHandler.withEventHandler(
nextHandler: eventHandler,
operation: { eventHandler in
try await self.perform(
request: request,
body: consumedBody.take(),
configuration: configuration,
eventHandler: eventHandler,
responseHandler: responseHandler
)
},
onRedirection: onRedirection,
onServerTrust: onServerTrust,
)
}
#else
/// Performs an HTTP request and processes the response.
///
/// This method executes the HTTP request with the specified configuration and event
/// handler, then invokes the response handler when the response headers are received.
/// The request and response bodies are streamed using the client's writer and reader types.
///
/// - Parameters:
/// - request: The HTTP request headers to send.
/// - body: The optional request body to send. When `nil`, no body is sent.
/// - configuration: The configuration settings for this request.
/// - eventHandler: The handler for processing events during request execution.
/// - responseHandler: The closure to process the response. This closure is invoked
/// when the response headers are received and can read the response body.
/// - onRedirection: The redirection handler overriding the one on `eventHandler`.
///
/// - Returns: The value returned by the response handler closure.
///
/// - Throws: An error if the request fails or if the response handler throws.
@_alwaysEmitIntoClient
public func perform<Return>(
request: HTTPRequest,
body: consuming HTTPClientRequestBody<RequestConcludingWriter>? = nil,
configuration: HTTPClientConfiguration = .init(),
eventHandler: consuming some HTTPClientEventHandler & ~Escapable & ~Copyable =
DefaultHTTPClientEventHandler(),
responseHandler: (HTTPResponse, consuming ResponseConcludingReader) async throws -> Return,
onRedirection: (
(_ response: HTTPResponse, _ newRequest: HTTPRequest) async throws ->
HTTPClientRedirectionAction
)? = nil,
) async throws -> Return {
// Since the element is ~Copyable but we don't have call-once closures
// we need to move it into an Optional and then take it out once
var consumedBody = consume body
return try await ScopedHTTPClientEventHandler.withEventHandler(
nextHandler: eventHandler,
operation: { eventHandler in
try await self.perform(
request: request,
body: consumedBody.take(),
configuration: configuration,
eventHandler: eventHandler,
responseHandler: responseHandler
)
},
onRedirection: onRedirection,
)
}
#endif
}
25 changes: 25 additions & 0 deletions Sources/HTTPAPIs/Client/HTTPClientCapability+RequestOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift HTTP API Proposal open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift HTTP API Proposal project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// The namespace for all protocols defining HTTP client capabilities.
@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
public enum HTTPClientCapability {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this namespace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is from the earlier discussion: #60 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, let's go ahead with this for now then.

/// The request options protocol.
///
/// Additional options supported by a subset of clients are defined in child
/// protocols to allow libraries to depend on a specific capabilities.
public protocol RequestOptions {
init()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift HTTP API Proposal open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift HTTP API Proposal project authors
// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand All @@ -12,17 +12,16 @@
//
//===----------------------------------------------------------------------===//

// We are using an exported import here since we don't want developers
// to have to import both this module and the HTTPAPIs module.
@_exported public import HTTPAPIs
public import NetworkTypes

/// This is the default shared HTTP client.
// TODO: Evaluate merging with the HTTPServer module https://github.com/apple/swift-http-api-proposal/issues/14
@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *)
public var httpClient: some HTTPClient {
#if canImport(Darwin)
URLSessionHTTPClient.shared
#else
UnsupportedPlatformHTTPClient()
#endif
extension HTTPClientCapability {
/// A protocol for HTTP request options that support TLS version constraints.
public protocol TLSVersionSelection: RequestOptions {
/// The minimum TLS version allowed for the connection.
var minimumTLSVersion: TLSVersion { get set }

/// The maximum TLS version allowed for the connection.
var maximumTLSVersion: TLSVersion { get set }
}
}
94 changes: 0 additions & 94 deletions Sources/HTTPAPIs/Client/HTTPClientConfiguration.swift

This file was deleted.

Loading
Loading