Coverage Report

Created: 2026-06-15 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/grpc-swift/Sources/GRPC/GRPCServerRequestRoutingHandler.swift
Line
Count
Source
1
/*
2
 * Copyright 2019, 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
import Logging
17
import NIOCore
18
import NIOHPACK
19
import NIOHTTP1
20
import NIOHTTP2
21
import SwiftProtobuf
22
23
/// Provides ``GRPCServerHandlerProtocol`` objects for the methods on a particular service name.
24
///
25
/// Implemented by the generated code.
26
public protocol CallHandlerProvider: AnyObject {
27
  /// The name of the service this object is providing methods for, including the package path.
28
  ///
29
  /// - Example: "io.grpc.Echo.EchoService"
30
  var serviceName: Substring { get }
31
32
  /// Returns a call handler for the method with the given name, if this service provider implements
33
  /// the given method. Returns `nil` if the method is not handled by this provider.
34
  /// - Parameters:
35
  ///   - name: The name of the method to handle.
36
  ///   - context: An opaque context providing components to construct the handler with.
37
  func handle(method name: Substring, context: CallHandlerContext) -> GRPCServerHandlerProtocol?
38
}
39
40
// This is public because it will be passed into generated code, all members are `internal` because
41
// the context will get passed from generated code back into gRPC library code and all members should
42
// be considered an implementation detail to the user.
43
public struct CallHandlerContext {
44
  @usableFromInline
45
  internal var errorDelegate: ServerErrorDelegate?
46
  @usableFromInline
47
  internal var logger: Logger
48
  @usableFromInline
49
  internal var encoding: ServerMessageEncoding
50
  @usableFromInline
51
  internal var eventLoop: EventLoop
52
  @usableFromInline
53
  internal var path: String
54
  @usableFromInline
55
  internal var remoteAddress: SocketAddress?
56
  @usableFromInline
57
  internal var responseWriter: GRPCServerResponseWriter
58
  @usableFromInline
59
  internal var allocator: ByteBufferAllocator
60
  @usableFromInline
61
  internal var closeFuture: EventLoopFuture<Void>
62
}
63
64
/// A call URI split into components.
65
struct CallPath {
66
  /// The name of the service to call.
67
  var service: String.UTF8View.SubSequence
68
  /// The name of the method to call.
69
  var method: String.UTF8View.SubSequence
70
71
  /// Character used to split the path into components.
72
106k
  private let pathSplitDelimiter = UInt8(ascii: "/")
73
74
  /// Split a path into service and method.
75
  /// Split is done in UTF8 as this turns out to be approximately 10x faster than a simple split.
76
  /// URI format: "/package.Servicename/MethodName"
77
106k
  init?(requestURI: String) {
78
106k
    var utf8View = requestURI.utf8[...]
79
106k
    // Check and remove the split character at the beginning.
80
106k
    guard let prefix = utf8View.trimPrefix(to: self.pathSplitDelimiter), prefix.isEmpty else {
81
2.17k
      return nil
82
104k
    }
83
104k
    guard let service = utf8View.trimPrefix(to: pathSplitDelimiter) else {
84
761
      return nil
85
103k
    }
86
103k
    guard let method = utf8View.trimPrefix(to: pathSplitDelimiter) else {
87
2.17k
      return nil
88
101k
    }
89
101k
90
101k
    self.service = service
91
101k
    self.method = method
92
101k
  }
93
}
94
95
extension Collection where Self == Self.SubSequence, Self.Element: Equatable {
96
  /// Trims out the prefix up to `separator`, and returns it.
97
  /// Sets self to the subsequence after the separator, and returns the subsequence before the separator.
98
  /// If self is empty returns `nil`
99
  /// - parameters:
100
  ///     - separator : The Element between the head which is returned and the rest which is left in self.
101
  /// - returns: SubSequence containing everything between the beginning and the first occurrence of
102
  /// `separator`.  If `separator` is not found this will be the entire Collection. If the collection is empty
103
  /// returns `nil`
104
314k
  mutating func trimPrefix(to separator: Element) -> SubSequence? {
105
314k
    guard !self.isEmpty else {
106
2.93k
      return nil
107
311k
    }
108
311k
    if let separatorIndex = self.firstIndex(of: separator) {
109
206k
      let indexAfterSeparator = self.index(after: separatorIndex)
110
206k
      defer { self = self[indexAfterSeparator...] }
111
206k
      return self[..<separatorIndex]
112
206k
    } else {
113
104k
      defer { self = self[self.endIndex...] }
114
104k
      return self[...]
115
104k
    }
116
311k
  }
117
}