@@ -47,246 +47,34 @@ public struct CallHandlerContext {
4747 internal var path : String
4848}
4949
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
24668 }
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 {
27370 return nil
27471 }
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
28774 }
28875
289- return HTTPResponseHead ( version: requestHead. version, status: . ok, headers: headers)
76+ self . service = service
77+ self . method = method
29078 }
29179}
29280
0 commit comments