TestServiceAsyncProvider.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. * Copyright 2021, 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. #if compiler(>=5.6)
  17. import Foundation
  18. import GRPC
  19. import GRPCInteroperabilityTestModels
  20. import NIOCore
  21. /// An async service provider for the gRPC interoperability test suite.
  22. ///
  23. /// See: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md#server
  24. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  25. public class TestServiceAsyncProvider: Grpc_Testing_TestServiceAsyncProvider {
  26. public var interceptors: Grpc_Testing_TestServiceServerInterceptorFactoryProtocol?
  27. public init() {}
  28. private static let echoMetadataNotImplemented = GRPCStatus(
  29. code: .unimplemented,
  30. message: "Echoing metadata is not yet supported"
  31. )
  32. /// Features that this server implements.
  33. ///
  34. /// Some 'features' are methods, whilst others optionally modify the outcome of those methods. The
  35. /// specification is not explicit about where these modifying features should be implemented (i.e.
  36. /// which methods should support them) and they are not listed in the individual method
  37. /// descriptions. As such implementation of these modifying features within each method is
  38. /// determined by the features required by each test.
  39. public static var implementedFeatures: Set<ServerFeature> {
  40. return [
  41. .emptyCall,
  42. .unaryCall,
  43. .streamingOutputCall,
  44. .streamingInputCall,
  45. .fullDuplexCall,
  46. .echoStatus,
  47. .compressedResponse,
  48. .compressedRequest,
  49. ]
  50. }
  51. /// Server implements `emptyCall` which immediately returns the empty message.
  52. public func emptyCall(
  53. request: Grpc_Testing_Empty,
  54. context: GRPCAsyncServerCallContext
  55. ) async throws -> Grpc_Testing_Empty {
  56. return Grpc_Testing_Empty()
  57. }
  58. /// Server implements `unaryCall` which immediately returns a `SimpleResponse` with a payload
  59. /// body of size `SimpleRequest.responseSize` bytes and type as appropriate for the
  60. /// `SimpleRequest.responseType`.
  61. ///
  62. /// If the server does not support the `responseType`, then it should fail the RPC with
  63. /// `INVALID_ARGUMENT`.
  64. public func unaryCall(
  65. request: Grpc_Testing_SimpleRequest,
  66. context: GRPCAsyncServerCallContext
  67. ) async throws -> Grpc_Testing_SimpleResponse {
  68. // We can't validate messages at the wire-encoding layer (i.e. where the compression byte is
  69. // set), so we have to check via the encoding header. Note that it is possible for the header
  70. // to be set and for the message to not be compressed.
  71. if request.expectCompressed.value, !context.request.headers.contains(name: "grpc-encoding") {
  72. throw GRPCStatus(
  73. code: .invalidArgument,
  74. message: "Expected compressed request, but 'grpc-encoding' was missing"
  75. )
  76. }
  77. // Should we enable compression? The C++ interoperability client only expects compression if
  78. // explicitly requested; we'll do the same.
  79. try await context.response.compressResponses(request.responseCompressed.value)
  80. if request.shouldEchoStatus {
  81. let code = GRPCStatus.Code(rawValue: numericCast(request.responseStatus.code)) ?? .unknown
  82. throw GRPCStatus(code: code, message: request.responseStatus.message)
  83. }
  84. if context.request.headers.shouldEchoMetadata {
  85. throw Self.echoMetadataNotImplemented
  86. }
  87. if case .UNRECOGNIZED = request.responseType {
  88. throw GRPCStatus(code: .invalidArgument, message: nil)
  89. }
  90. return Grpc_Testing_SimpleResponse.with { response in
  91. response.payload = Grpc_Testing_Payload.with { payload in
  92. payload.body = Data(repeating: 0, count: numericCast(request.responseSize))
  93. payload.type = request.responseType
  94. }
  95. }
  96. }
  97. /// Server gets the default `SimpleRequest` proto as the request. The content of the request is
  98. /// ignored. It returns the `SimpleResponse` proto with the payload set to current timestamp.
  99. /// The timestamp is an integer representing current time with nanosecond resolution. This
  100. /// integer is formated as ASCII decimal in the response. The format is not really important as
  101. /// long as the response payload is different for each request. In addition it adds cache control
  102. /// headers such that the response can be cached by proxies in the response path. Server should
  103. /// be behind a caching proxy for this test to pass. Currently we set the max-age to 60 seconds.
  104. public func cacheableUnaryCall(
  105. request: Grpc_Testing_SimpleRequest,
  106. context: GRPCAsyncServerCallContext
  107. ) async throws -> Grpc_Testing_SimpleResponse {
  108. throw GRPCStatus(
  109. code: .unimplemented,
  110. message: "'cacheableUnaryCall' requires control of the initial metadata which isn't supported"
  111. )
  112. }
  113. /// Server implements `streamingOutputCall` by replying, in order, with one
  114. /// `StreamingOutputCallResponse` for each `ResponseParameter`s in `StreamingOutputCallRequest`.
  115. /// Each `StreamingOutputCallResponse` should have a payload body of size `ResponseParameter.size`
  116. /// bytes, as specified by its respective `ResponseParameter`. After sending all responses, it
  117. /// closes with OK.
  118. public func streamingOutputCall(
  119. request: Grpc_Testing_StreamingOutputCallRequest,
  120. responseStream: GRPCAsyncResponseStreamWriter<Grpc_Testing_StreamingOutputCallResponse>,
  121. context: GRPCAsyncServerCallContext
  122. ) async throws {
  123. for responseParameter in request.responseParameters {
  124. let response = Grpc_Testing_StreamingOutputCallResponse.with { response in
  125. response.payload = Grpc_Testing_Payload.with { payload in
  126. payload.body = Data(repeating: 0, count: numericCast(responseParameter.size))
  127. }
  128. }
  129. // Should we enable compression? The C++ interoperability client only expects compression if
  130. // explicitly requested; we'll do the same.
  131. let compression: Compression = responseParameter.compressed.value ? .enabled : .disabled
  132. try await responseStream.send(response, compression: compression)
  133. }
  134. }
  135. /// Server implements `streamingInputCall` which upon half close immediately returns a
  136. /// `StreamingInputCallResponse` where `aggregatedPayloadSize` is the sum of all request payload
  137. /// bodies received.
  138. public func streamingInputCall(
  139. requestStream: GRPCAsyncRequestStream<Grpc_Testing_StreamingInputCallRequest>,
  140. context: GRPCAsyncServerCallContext
  141. ) async throws -> Grpc_Testing_StreamingInputCallResponse {
  142. var aggregatePayloadSize = 0
  143. for try await request in requestStream {
  144. if request.expectCompressed.value {
  145. guard context.request.headers.contains(name: "grpc-encoding") else {
  146. throw GRPCStatus(
  147. code: .invalidArgument,
  148. message: "Expected compressed request, but 'grpc-encoding' was missing"
  149. )
  150. }
  151. }
  152. aggregatePayloadSize += request.payload.body.count
  153. }
  154. return Grpc_Testing_StreamingInputCallResponse.with { response in
  155. response.aggregatedPayloadSize = numericCast(aggregatePayloadSize)
  156. }
  157. }
  158. /// Server implements `fullDuplexCall` by replying, in order, with one
  159. /// `StreamingOutputCallResponse` for each `ResponseParameter`s in each
  160. /// `StreamingOutputCallRequest`. Each `StreamingOutputCallResponse` should have a payload body
  161. /// of size `ResponseParameter.size` bytes, as specified by its respective `ResponseParameter`s.
  162. /// After receiving half close and sending all responses, it closes with OK.
  163. public func fullDuplexCall(
  164. requestStream: GRPCAsyncRequestStream<Grpc_Testing_StreamingOutputCallRequest>,
  165. responseStream: GRPCAsyncResponseStreamWriter<Grpc_Testing_StreamingOutputCallResponse>,
  166. context: GRPCAsyncServerCallContext
  167. ) async throws {
  168. // We don't have support for this yet so just fail the call.
  169. if context.request.headers.shouldEchoMetadata {
  170. throw Self.echoMetadataNotImplemented
  171. }
  172. for try await request in requestStream {
  173. if request.shouldEchoStatus {
  174. let code = GRPCStatus.Code(rawValue: numericCast(request.responseStatus.code))
  175. let status = GRPCStatus(code: code ?? .unknown, message: request.responseStatus.message)
  176. throw status
  177. } else {
  178. for responseParameter in request.responseParameters {
  179. let response = Grpc_Testing_StreamingOutputCallResponse.with { response in
  180. response.payload = .zeros(count: numericCast(responseParameter.size))
  181. }
  182. try await responseStream.send(response)
  183. }
  184. }
  185. }
  186. }
  187. /// This is not implemented as it is not described in the specification.
  188. ///
  189. /// See: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
  190. public func halfDuplexCall(
  191. requestStream: GRPCAsyncRequestStream<Grpc_Testing_StreamingOutputCallRequest>,
  192. responseStream: GRPCAsyncResponseStreamWriter<Grpc_Testing_StreamingOutputCallResponse>,
  193. context: GRPCAsyncServerCallContext
  194. ) async throws {
  195. throw GRPCStatus(
  196. code: .unimplemented,
  197. message: "'halfDuplexCall' was not described in the specification"
  198. )
  199. }
  200. }
  201. #endif // compiler(>=5.6)