Skip to content

Commit 6d8108c

Browse files
authored
Interceptor APIs for client and server (#1668)
1 parent b92a24c commit 6d8108c

12 files changed

+215
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2023, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/// A type that intercepts requests and response for clients.
18+
///
19+
/// Interceptors allow you to inspect and modify requests and responses. Requests are intercepted
20+
/// before they are handed to a transport and responses are intercepted after they have been
21+
/// received from the transport. They are typically used for cross-cutting concerns like injecting
22+
/// metadata, validating messages, logging additional data, and tracing.
23+
///
24+
/// Interceptors are registered with a client and apply to all RPCs. If you need to modify the
25+
/// behavior of an interceptor on a per-RPC basis then you can use the
26+
/// ``ClientInterceptorContext/descriptor`` to determine which RPC is being called and
27+
/// conditionalise behavior accordingly.
28+
///
29+
/// - TODO: Update example and documentation to show how to register an interceptor.
30+
///
31+
/// Some examples of simple interceptors follow.
32+
///
33+
/// ## Metadata injection
34+
///
35+
/// A common use-case for client interceptors is injecting metadata into a request.
36+
///
37+
/// ```swift
38+
/// struct MetadataInjectingClientInterceptor: ClientInterceptor {
39+
/// let key: String
40+
/// let fetchMetadata: @Sendable () async -> String
41+
///
42+
/// func intercept<Input: Sendable, Output: Sendable>(
43+
/// request: ClientRequest.Stream<Input>,
44+
/// context: ClientInterceptorContext,
45+
/// next: @Sendable (
46+
/// _ request: ClientRequest.Stream<Input>,
47+
/// _ context: ClientInterceptorContext
48+
/// ) async throws -> ClientResponse.Stream<Output>
49+
/// ) async throws -> ClientResponse.Stream<Output> {
50+
/// // Fetch the metadata value and attach it.
51+
/// let value = await self.fetchMetadata()
52+
/// var request = request
53+
/// request.metadata[self.key] = value
54+
///
55+
/// // Forward the request to the next interceptor.
56+
/// return try await next(request, context)
57+
/// }
58+
/// }
59+
/// ```
60+
///
61+
/// Interceptors can also be used to print information about RPCs.
62+
///
63+
/// ## Logging interceptor
64+
///
65+
/// ```swift
66+
/// struct LoggingClientInterceptor: ClientInterceptor {
67+
/// func intercept<Input: Sendable, Output: Sendable>(
68+
/// request: ClientRequest.Stream<Input>,
69+
/// context: ClientInterceptorContext,
70+
/// next: @Sendable (
71+
/// _ request: ClientRequest.Stream<Input>,
72+
/// _ context: ClientInterceptorContext
73+
/// ) async throws -> ClientResponse.Stream<Output>
74+
/// ) async throws -> ClientResponse.Stream<Output> {
75+
/// print("Invoking method '\(context.descriptor)'")
76+
/// let response = try await next(request, context)
77+
///
78+
/// switch response.accepted {
79+
/// case .success:
80+
/// print("Server accepted RPC for processing")
81+
/// case .failure(let error):
82+
/// print("Server rejected RPC with error '\(error)'")
83+
/// }
84+
///
85+
/// return response
86+
/// }
87+
/// }
88+
/// ```
89+
///
90+
/// For server-side interceptors see ``ServerInterceptor``.
91+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
92+
public protocol ClientInterceptor: Sendable {
93+
/// Intercept a request object.
94+
///
95+
/// - Parameters:
96+
/// - request: The request object.
97+
/// - context: Additional context about the request, including a descriptor
98+
/// of the method being called.
99+
/// - next: A closure to invoke to hand off the request and context to the next
100+
/// interceptor in the chain.
101+
/// - Returns: A response object.
102+
func intercept<Input: Sendable, Output: Sendable>(
103+
request: ClientRequest.Stream<Input>,
104+
context: ClientInterceptorContext,
105+
next: @Sendable (
106+
_ request: ClientRequest.Stream<Input>,
107+
_ context: ClientInterceptorContext
108+
) async throws -> ClientResponse.Stream<Output>
109+
) async throws -> ClientResponse.Stream<Output>
110+
}
111+
112+
/// A context passed to client interceptors containing additional information about the RPC.
113+
public struct ClientInterceptorContext: Sendable {
114+
/// A description of the method being called.
115+
public var descriptor: MethodDescriptor
116+
117+
/// Create a new client interceptor context.
118+
public init(descriptor: MethodDescriptor) {
119+
self.descriptor = descriptor
120+
}
121+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2023, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/// A type that intercepts requests and response for server.
18+
///
19+
/// Interceptors allow you to inspect and modify requests and responses. Requests are intercepted
20+
/// after they have been received by the transport and responses are intercepted after they have
21+
/// been returned from a service. They are typically used for cross-cutting concerns like filtering
22+
/// requests, validating messages, logging additional data, and tracing.
23+
///
24+
/// Interceptors are registered with the server apply to all RPCs. If you need to modify the
25+
/// behavior of an interceptor on a per-RPC basis then you can use the
26+
/// ``ServerInterceptorContext/descriptor`` to determine which RPC is being called and
27+
/// conditionalise behavior accordingly.
28+
///
29+
/// - TODO: Update example and documentation to show how to register an interceptor.
30+
///
31+
/// ## RPC filtering
32+
///
33+
/// A common use of server-side interceptors is to filter requests from clients. Interceptors can
34+
/// reject requests which are invalid without service code being called. The following example
35+
/// demonstrates this.
36+
///
37+
/// ```swift
38+
/// struct AuthServerInterceptor: Sendable {
39+
/// let isAuthorized: @Sendable (String, MethodDescriptor) async throws -> Void
40+
///
41+
/// func intercept<Input: Sendable, Output: Sendable>(
42+
/// request: ServerRequest.Stream<Input>,
43+
/// context: ServerInterceptorContext,
44+
/// next: @Sendable (
45+
/// _ request: ServerRequest.Stream<Input>,
46+
/// _ context: ServerInterceptorContext
47+
/// ) async throws -> ServerResponse.Stream<Output>
48+
/// ) async throws -> ServerResponse.Stream<Output> {
49+
/// // Extract the auth token.
50+
/// guard let token = request.metadata["authorization"] else {
51+
/// throw RPCError(code: .unauthenticated, message: "Not authenticated")
52+
/// }
53+
///
54+
/// // Check whether it's valid.
55+
/// try await self.isAuthorized(token, context.descriptor)
56+
///
57+
/// // Forward the request.
58+
/// return try await next(request, context)
59+
/// }
60+
/// }
61+
/// ```
62+
///
63+
/// For server-side interceptors see ``ClientInterceptor``.
64+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
65+
public protocol ServerInterceptor: Sendable {
66+
/// Intercept a request object.
67+
///
68+
/// - Parameters:
69+
/// - request: The request object.
70+
/// - context: Additional context about the request, including a descriptor
71+
/// of the method being called.
72+
/// - next: A closure to invoke to hand off the request and context to the next
73+
/// interceptor in the chain.
74+
/// - Returns: A response object.
75+
func intercept<Input: Sendable, Output: Sendable>(
76+
request: ServerRequest.Stream<Input>,
77+
context: ServerInterceptorContext,
78+
next: @Sendable (
79+
_ request: ServerRequest.Stream<Input>,
80+
_ context: ServerInterceptorContext
81+
) async throws -> ServerResponse.Stream<Output>
82+
) async throws -> ServerResponse.Stream<Output>
83+
}
84+
85+
/// A context passed to client interceptors containing additional information about the RPC.
86+
public struct ServerInterceptorContext: Sendable {
87+
/// A description of the method being called.
88+
public var descriptor: MethodDescriptor
89+
90+
/// Create a new client interceptor context.
91+
public init(descriptor: MethodDescriptor) {
92+
self.descriptor = descriptor
93+
}
94+
}

0 commit comments

Comments
 (0)