@@ -47,246 +47,34 @@ public struct CallHandlerContext {
47
47
internal var path : String
48
48
}
49
49
50
- /// Attempts to route a request to a user-provided call handler. Also validates that the request has
51
- /// a suitable 'content-type' for gRPC.
52
- ///
53
- /// Once the request headers are available, asks the `CallHandlerProvider` corresponding to the request's service name
54
- /// for a `GRPCCallHandler` object. That object is then forwarded the individual gRPC messages.
55
- ///
56
- /// After the pipeline has been configured with the `GRPCCallHandler`, this handler removes itself
57
- /// from the pipeline.
58
- public final class GRPCServerRequestRoutingHandler {
59
- private let logger : Logger
60
- private let servicesByName : [ Substring : CallHandlerProvider ]
61
- private let encoding : ServerMessageEncoding
62
- private weak var errorDelegate : ServerErrorDelegate ?
63
-
64
- private enum State : Equatable {
65
- case notConfigured
66
- case configuring( [ InboundOut ] )
67
- }
68
-
69
- private var state : State = . notConfigured
70
-
71
- public init (
72
- servicesByName: [ Substring : CallHandlerProvider ] ,
73
- encoding: ServerMessageEncoding ,
74
- errorDelegate: ServerErrorDelegate ? ,
75
- logger: Logger
76
- ) {
77
- self . servicesByName = servicesByName
78
- self . encoding = encoding
79
- self . errorDelegate = errorDelegate
80
- self . logger = logger
81
- }
82
- }
83
-
84
- extension GRPCServerRequestRoutingHandler : ChannelInboundHandler , RemovableChannelHandler {
85
- public typealias InboundIn = HTTPServerRequestPart
86
- public typealias InboundOut = HTTPServerRequestPart
87
- public typealias OutboundOut = HTTPServerResponsePart
88
-
89
- public func errorCaught( context: ChannelHandlerContext , error: Error ) {
90
- let status : GRPCStatus
91
- if let errorWithContext = error as? GRPCError . WithContext {
92
- self . errorDelegate? . observeLibraryError ( errorWithContext. error)
93
- status = errorWithContext. error. makeGRPCStatus ( )
94
- } else {
95
- self . errorDelegate? . observeLibraryError ( error)
96
- status = ( error as? GRPCStatusTransformable ) ? . makeGRPCStatus ( ) ?? . processingError
97
- }
98
-
99
- switch self . state {
100
- case . notConfigured:
101
- // We don't know what protocol we're speaking at this point. We'll just have to close the
102
- // channel.
103
- ( )
104
-
105
- case let . configuring( messages) :
106
- // first! is fine here: we only go from `.notConfigured` to `.configuring` when we receive
107
- // and validate the request head.
108
- let head = messages. compactMap { part -> HTTPRequestHead ? in
109
- switch part {
110
- case let . head( head) :
111
- return head
112
- default :
113
- return nil
114
- }
115
- } . first!
116
-
117
- let responseHead = self . makeResponseHead ( requestHead: head, status: status)
118
- context. write ( self . wrapOutboundOut ( . head( responseHead) ) , promise: nil )
119
- context. write ( self . wrapOutboundOut ( . end( nil ) ) , promise: nil )
120
- context. flush ( )
121
- }
122
-
123
- context. close ( mode: . all, promise: nil )
124
- }
125
-
126
- public func channelRead( context: ChannelHandlerContext , data: NIOAny ) {
127
- let requestPart = self . unwrapInboundIn ( data)
128
- switch requestPart {
129
- case let . head( requestHead) :
130
- precondition ( self . state == . notConfigured)
131
-
132
- // Validate the 'content-type' is related to gRPC before proceeding.
133
- let maybeContentType = requestHead. headers. first ( name: GRPCHeaderName . contentType)
134
- guard let contentType = maybeContentType,
135
- contentType. starts ( with: ContentType . commonPrefix) else {
136
- self . logger. warning (
137
- " received request whose 'content-type' does not exist or start with ' \( ContentType . commonPrefix) ' " ,
138
- metadata: [ " content-type " : " \( String ( describing: maybeContentType) ) " ]
139
- )
140
-
141
- // From: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
142
- //
143
- // If 'content-type' does not begin with "application/grpc", gRPC servers SHOULD respond
144
- // with HTTP status of 415 (Unsupported Media Type). This will prevent other HTTP/2
145
- // clients from interpreting a gRPC error response, which uses status 200 (OK), as
146
- // successful.
147
- let responseHead = HTTPResponseHead (
148
- version: requestHead. version,
149
- status: . unsupportedMediaType
150
- )
151
-
152
- // Fail the call. Note: we're not speaking gRPC here, so no status or message.
153
- context. write ( self . wrapOutboundOut ( . head( responseHead) ) , promise: nil )
154
- context. writeAndFlush ( self . wrapOutboundOut ( . end( nil ) ) , promise: nil )
155
- return
156
- }
157
-
158
- // Do we know how to handle this RPC?
159
- guard let callHandler = self . makeCallHandler (
160
- channel: context. channel,
161
- requestHead: requestHead
162
- ) else {
163
- self . logger. warning (
164
- " unable to make call handler; the RPC is not implemented on this server " ,
165
- metadata: [ " uri " : " \( requestHead. uri) " ]
166
- )
167
-
168
- let status = GRPCError . RPCNotImplemented ( rpc: requestHead. uri) . makeGRPCStatus ( )
169
- let responseHead = self . makeResponseHead ( requestHead: requestHead, status: status)
170
-
171
- // Write back a 'trailers-only' response.
172
- context. write ( self . wrapOutboundOut ( . head( responseHead) ) , promise: nil )
173
- context. writeAndFlush ( self . wrapOutboundOut ( . end( nil ) ) , promise: nil )
174
- return
175
- }
176
-
177
- self . logger. debug ( " received request head, configuring pipeline " )
178
-
179
- // Buffer the request head; we'll replay it in the next handler when we're removed from the
180
- // pipeline.
181
- self . state = . configuring( [ requestPart] )
182
-
183
- // Configure the rest of the pipeline to serve the RPC.
184
- let httpToGRPC = HTTP1ToGRPCServerCodec ( encoding: self . encoding, logger: self . logger)
185
- context. pipeline. addHandlers ( [ httpToGRPC, callHandler] , position: . after( self ) )
186
- . whenSuccess {
187
- context. pipeline. removeHandler ( self , promise: nil )
188
- }
189
-
190
- case . body, . end:
191
- switch self . state {
192
- case . notConfigured:
193
- // We can reach this point if we're receiving messages for a method that isn't implemented,
194
- // in which case we just drop the messages; our response should already be in-flight.
195
- ( )
196
-
197
- case var . configuring( buffer) :
198
- // We received a message while the pipeline was being configured; hold on to it while we
199
- // finish configuring the pipeline.
200
- buffer. append ( requestPart)
201
- self . state = . configuring( buffer)
202
- }
203
- }
204
- }
205
-
206
- public func handlerRemoved( context: ChannelHandlerContext ) {
207
- switch self . state {
208
- case . notConfigured:
209
- ( )
210
-
211
- case let . configuring( messages) :
212
- for message in messages {
213
- context. fireChannelRead ( self . wrapInboundOut ( message) )
214
- }
215
- }
216
- }
217
-
218
- /// A call URI split into components.
219
- struct CallPath {
220
- /// The name of the service to call.
221
- var service : String . UTF8View . SubSequence
222
- /// The name of the method to call.
223
- var method : String . UTF8View . SubSequence
224
-
225
- /// Charater used to split the path into components.
226
- private let pathSplitDelimiter = UInt8 ( ascii: " / " )
227
-
228
- /// Split a path into service and method.
229
- /// Split is done in UTF8 as this turns out to be approximately 10x faster than a simple split.
230
- /// URI format: "/package.Servicename/MethodName"
231
- init ? ( requestURI: String ) {
232
- var utf8View = requestURI. utf8 [ ... ]
233
- // Check and remove the split character at the beginning.
234
- guard let prefix = utf8View. trimPrefix ( to: self . pathSplitDelimiter) , prefix. isEmpty else {
235
- return nil
236
- }
237
- guard let service = utf8View. trimPrefix ( to: pathSplitDelimiter) else {
238
- return nil
239
- }
240
- guard let method = utf8View. trimPrefix ( to: pathSplitDelimiter) else {
241
- return nil
242
- }
243
-
244
- self . service = service
245
- self . method = method
50
+ /// A call URI split into components.
51
+ struct CallPath {
52
+ /// The name of the service to call.
53
+ var service : String . UTF8View . SubSequence
54
+ /// The name of the method to call.
55
+ var method : String . UTF8View . SubSequence
56
+
57
+ /// Charater used to split the path into components.
58
+ private let pathSplitDelimiter = UInt8 ( ascii: " / " )
59
+
60
+ /// Split a path into service and method.
61
+ /// Split is done in UTF8 as this turns out to be approximately 10x faster than a simple split.
62
+ /// URI format: "/package.Servicename/MethodName"
63
+ init ? ( requestURI: String ) {
64
+ var utf8View = requestURI. utf8 [ ... ]
65
+ // Check and remove the split character at the beginning.
66
+ guard let prefix = utf8View. trimPrefix ( to: self . pathSplitDelimiter) , prefix. isEmpty else {
67
+ return nil
246
68
}
247
- }
248
-
249
- private func makeCallHandler( channel: Channel , requestHead: HTTPRequestHead ) -> GRPCCallHandler ? {
250
- // URI format: "/package.Servicename/MethodName", resulting in the following components separated by a slash:
251
- // - uriComponents[0]: empty
252
- // - uriComponents[1]: service name (including the package name);
253
- // `CallHandlerProvider`s should provide the service name including the package name.
254
- // - uriComponents[2]: method name.
255
- self . logger. debug ( " making call handler " , metadata: [ " path " : " \( requestHead. uri) " ] )
256
- let uriComponents = CallPath ( requestURI: requestHead. uri)
257
-
258
- let context = CallHandlerContext (
259
- errorDelegate: self . errorDelegate,
260
- logger: self . logger,
261
- encoding: self . encoding,
262
- eventLoop: channel. eventLoop,
263
- path: requestHead. uri
264
- )
265
-
266
- guard let callPath = uriComponents,
267
- let providerForServiceName = servicesByName [ String . SubSequence ( callPath. service) ] ,
268
- let callHandler = providerForServiceName. handleMethod (
269
- String . SubSequence ( callPath. method) ,
270
- callHandlerContext: context
271
- ) else {
272
- self . logger. notice ( " could not create handler " , metadata: [ " path " : " \( requestHead. uri) " ] )
69
+ guard let service = utf8View. trimPrefix ( to: pathSplitDelimiter) else {
273
70
return nil
274
71
}
275
- return callHandler
276
- }
277
-
278
- private func makeResponseHead( requestHead: HTTPRequestHead ,
279
- status: GRPCStatus ) -> HTTPResponseHead {
280
- var headers : HTTPHeaders = [
281
- GRPCHeaderName . contentType: ContentType . protobuf. canonicalValue,
282
- GRPCHeaderName . statusCode: " \( status. code. rawValue) " ,
283
- ]
284
-
285
- if let message = status. message. flatMap ( GRPCStatusMessageMarshaller . marshall) {
286
- headers. add ( name: GRPCHeaderName . statusMessage, value: message)
72
+ guard let method = utf8View. trimPrefix ( to: pathSplitDelimiter) else {
73
+ return nil
287
74
}
288
75
289
- return HTTPResponseHead ( version: requestHead. version, status: . ok, headers: headers)
76
+ self . service = service
77
+ self . method = method
290
78
}
291
79
}
292
80
0 commit comments