Improvements and fixes (#223)
Some checks failed
CI / Test (macos-latest, Swift 6.1.0) (push) Has been cancelled
CI / Test (ubuntu-latest, Swift 6.1.0) (push) Has been cancelled
CI / MCP Conformance Tests (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / Linux Static SDK Build (6.1.2 - ubuntu-latest) (push) Has been cancelled
Some checks failed
CI / Test (macos-latest, Swift 6.1.0) (push) Has been cancelled
CI / Test (ubuntu-latest, Swift 6.1.0) (push) Has been cancelled
CI / MCP Conformance Tests (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / Linux Static SDK Build (6.1.2 - ubuntu-latest) (push) Has been cancelled
* feat: added http transport context to the task local execution * fix: added refresh_token to the grantTypes for DCR * chore: updated documentation
This commit is contained in:
parent
cb6a62f7c2
commit
a0ae212ebf
4 changed files with 345 additions and 14 deletions
|
|
@ -79,7 +79,7 @@ struct OAuthClientRegistrar: Sendable {
|
|||
let responseTypes: [String]
|
||||
switch configuration.grantType {
|
||||
case .authorizationCode:
|
||||
grantTypes = [OAuthGrantTypeValue.authorizationCode]
|
||||
grantTypes = [OAuthGrantTypeValue.authorizationCode, OAuthGrantTypeValue.refreshToken]
|
||||
responseTypes = [OAuthParameterName.code]
|
||||
case .clientCredentials:
|
||||
grantTypes = [OAuthGrantTypeValue.clientCredentials]
|
||||
|
|
|
|||
|
|
@ -207,6 +207,24 @@ struct SSEEvent: Sendable {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - HTTP Context Providing
|
||||
|
||||
/// A transport that can surface the originating HTTP request for an in-flight JSON-RPC
|
||||
/// request, keyed by its JSON-RPC id.
|
||||
///
|
||||
/// The server consults this during dispatch so that registered method handlers can
|
||||
/// observe the HTTP request that triggered them (headers, auth, path, body) via
|
||||
/// ``Server/currentHandlerContext``, without changing the `withMethodHandler` signature.
|
||||
///
|
||||
/// Only JSON-RPC *requests* (with an id) are addressable — notifications have no id and
|
||||
/// are not correlated.
|
||||
public protocol HTTPContextProviding: Sendable {
|
||||
/// Returns the HTTP request associated with the given JSON-RPC id, if the
|
||||
/// transport still has it on hand. Implementations must return `nil` once the
|
||||
/// corresponding response has been delivered or the session has terminated.
|
||||
func httpRequestContext(for id: ID) async -> HTTPRequest?
|
||||
}
|
||||
|
||||
// MARK: - JSON-RPC Message Classification
|
||||
|
||||
/// Classifies a raw JSON-RPC message for routing purposes.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import Logging
|
|||
/// and receive `HTTPResponse` values to convert to your framework's native types.
|
||||
/// For SSE responses, the `.stream` case provides an `AsyncThrowingStream<Data, Error>`
|
||||
/// to pipe to the client.
|
||||
public actor StatefulHTTPServerTransport: Transport {
|
||||
public actor StatefulHTTPServerTransport: Transport, HTTPContextProviding {
|
||||
public nonisolated let logger: Logger
|
||||
|
||||
// MARK: - Dependencies
|
||||
|
|
@ -54,6 +54,11 @@ public actor StatefulHTTPServerTransport: Transport {
|
|||
/// Maps request ID → SSE stream continuation for active POST request streams.
|
||||
private var requestSSEContinuations: [String: AsyncThrowingStream<Data, Swift.Error>.Continuation] = [:]
|
||||
|
||||
/// Maps request ID → originating HTTP request, surfaced to handlers via
|
||||
/// ``Server/currentHTTPContext``. Cleared when the response is routed,
|
||||
/// the stream is closed, or the session terminates.
|
||||
private var httpRequestContexts: [String: HTTPRequest] = [:]
|
||||
|
||||
// MARK: - Standalone GET SSE stream
|
||||
|
||||
/// The standalone SSE stream continuation for server-initiated messages.
|
||||
|
|
@ -280,6 +285,7 @@ public actor StatefulHTTPServerTransport: Transport {
|
|||
// Create SSE stream for this request
|
||||
let (sseStream, sseContinuation) = AsyncThrowingStream<Data, Swift.Error>.makeStream()
|
||||
requestSSEContinuations[requestID] = sseContinuation
|
||||
httpRequestContexts[requestID] = request
|
||||
|
||||
// Extract protocol version for priming event decision
|
||||
let protocolVersion = extractProtocolVersion(from: body, request: request)
|
||||
|
|
@ -406,6 +412,7 @@ public actor StatefulHTTPServerTransport: Transport {
|
|||
continuation.finish()
|
||||
requestSSEContinuations.removeValue(forKey: requestID)
|
||||
}
|
||||
httpRequestContexts.removeValue(forKey: requestID)
|
||||
}
|
||||
|
||||
private func routeServerInitiatedMessage(_ data: Data) {
|
||||
|
|
@ -535,6 +542,13 @@ public actor StatefulHTTPServerTransport: Transport {
|
|||
guard let continuation = requestSSEContinuations[requestID] else { return }
|
||||
continuation.finish()
|
||||
requestSSEContinuations.removeValue(forKey: requestID)
|
||||
httpRequestContexts.removeValue(forKey: requestID)
|
||||
}
|
||||
|
||||
// MARK: - HTTPContextProviding
|
||||
|
||||
public func httpRequestContext(for id: ID) -> HTTPRequest? {
|
||||
httpRequestContexts[id.description]
|
||||
}
|
||||
|
||||
// MARK: - Termination
|
||||
|
|
@ -552,6 +566,7 @@ public actor StatefulHTTPServerTransport: Transport {
|
|||
continuation.finish()
|
||||
}
|
||||
requestSSEContinuations.removeAll()
|
||||
httpRequestContexts.removeAll()
|
||||
|
||||
// Close standalone GET stream
|
||||
standaloneSSEContinuation?.finish()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import Logging
|
|||
/// - Session management is handled externally or not needed
|
||||
///
|
||||
/// For full streaming and session support, use ``StatefulHTTPServerTransport`` instead.
|
||||
public actor StatelessHTTPServerTransport: Transport {
|
||||
public actor StatelessHTTPServerTransport: Transport, HTTPContextProviding {
|
||||
public nonisolated let logger: Logger
|
||||
|
||||
// MARK: - Dependencies
|
||||
|
|
@ -52,6 +52,11 @@ public actor StatelessHTTPServerTransport: Transport {
|
|||
/// When the server calls `send()` with a response, the matching continuation is resumed.
|
||||
private var responseWaiters: [String: CheckedContinuation<Data, any Error>] = [:]
|
||||
|
||||
/// Maps request ID → originating HTTP request, surfaced to handlers via
|
||||
/// ``Server/currentHTTPContext``. Entries live only while a JSON-RPC request
|
||||
/// is in flight.
|
||||
private var httpRequestContexts: [String: HTTPRequest] = [:]
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
/// Creates a new stateless HTTP server transport.
|
||||
|
|
@ -207,11 +212,16 @@ public actor StatelessHTTPServerTransport: Transport {
|
|||
return .accepted()
|
||||
|
||||
case .request(let id, _):
|
||||
return await handleJSONRPCRequest(body, requestID: id)
|
||||
return await handleJSONRPCRequest(body, requestID: id, request: request)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleJSONRPCRequest(_ body: Data, requestID: String) async -> HTTPResponse {
|
||||
private func handleJSONRPCRequest(
|
||||
_ body: Data,
|
||||
requestID: String,
|
||||
request: HTTPRequest
|
||||
) async -> HTTPResponse {
|
||||
httpRequestContexts[requestID] = request
|
||||
// Yield the incoming message to the server
|
||||
incomingContinuation.yield(body)
|
||||
|
||||
|
|
@ -222,15 +232,23 @@ public actor StatelessHTTPServerTransport: Transport {
|
|||
responseWaiters[requestID] = continuation
|
||||
}
|
||||
} catch {
|
||||
httpRequestContexts.removeValue(forKey: requestID)
|
||||
return .error(
|
||||
statusCode: 500,
|
||||
.internalError("Error processing request: \(error.localizedDescription)")
|
||||
)
|
||||
}
|
||||
|
||||
httpRequestContexts.removeValue(forKey: requestID)
|
||||
return .data(responseData, headers: [HTTPHeaderName.contentType: ContentType.json])
|
||||
}
|
||||
|
||||
// MARK: - HTTPContextProviding
|
||||
|
||||
public func httpRequestContext(for id: ID) -> HTTPRequest? {
|
||||
httpRequestContexts[id.description]
|
||||
}
|
||||
|
||||
// MARK: - Termination
|
||||
|
||||
private func terminate() async {
|
||||
|
|
@ -245,6 +263,7 @@ public actor StatelessHTTPServerTransport: Transport {
|
|||
logger.debug("Cancelled waiter for request", metadata: ["requestID": "\(id)"])
|
||||
}
|
||||
responseWaiters.removeAll()
|
||||
httpRequestContexts.removeAll()
|
||||
|
||||
// Close incoming stream
|
||||
incomingContinuation.finish()
|
||||
|
|
|
|||
Loading…
Reference in a new issue