Echo.swift 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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 ArgumentParser
  18. import EchoImplementation
  19. import EchoModel
  20. import GRPC
  21. import GRPCSampleData
  22. import NIOCore
  23. import NIOPosix
  24. #if canImport(NIOSSL)
  25. import NIOSSL
  26. #endif
  27. // MARK: - Argument parsing
  28. enum RPC: String, ExpressibleByArgument {
  29. case get
  30. case collect
  31. case expand
  32. case update
  33. }
  34. @main
  35. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  36. struct Echo: AsyncParsableCommand {
  37. static var configuration = CommandConfiguration(
  38. abstract: "An example to run and call a simple gRPC service for echoing messages.",
  39. subcommands: [Server.self, Client.self]
  40. )
  41. struct Server: AsyncParsableCommand {
  42. static var configuration = CommandConfiguration(
  43. abstract: "Start a gRPC server providing the Echo service."
  44. )
  45. @Option(help: "The port to listen on for new connections")
  46. var port = 1234
  47. @Flag(help: "Whether TLS should be used or not")
  48. var tls = false
  49. func run() async throws {
  50. let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  51. defer {
  52. try! group.syncShutdownGracefully()
  53. }
  54. do {
  55. try await startEchoServer(group: group, port: self.port, useTLS: self.tls)
  56. } catch {
  57. print("Error running server: \(error)")
  58. }
  59. }
  60. }
  61. struct Client: AsyncParsableCommand {
  62. static var configuration = CommandConfiguration(
  63. abstract: "Calls an RPC on the Echo server."
  64. )
  65. @Option(help: "The port to connect to")
  66. var port = 1234
  67. @Flag(help: "Whether TLS should be used or not")
  68. var tls = false
  69. @Flag(help: "Whether interceptors should be used, see 'docs/interceptors-tutorial.md'.")
  70. var intercept = false
  71. @Option(help: "RPC to call ('get', 'collect', 'expand', 'update').")
  72. var rpc: RPC = .get
  73. @Option(help: "How many RPCs to do.")
  74. var iterations: Int = 1
  75. @Argument(help: "Message to echo")
  76. var message: String
  77. func run() async throws {
  78. let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  79. defer {
  80. try! group.syncShutdownGracefully()
  81. }
  82. let client = makeClient(
  83. group: group,
  84. port: self.port,
  85. useTLS: self.tls,
  86. useInterceptor: self.intercept
  87. )
  88. defer {
  89. try! client.channel.close().wait()
  90. }
  91. for _ in 0 ..< self.iterations {
  92. await callRPC(self.rpc, using: client, message: self.message)
  93. }
  94. }
  95. }
  96. }
  97. // MARK: - Server
  98. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  99. func startEchoServer(group: EventLoopGroup, port: Int, useTLS: Bool) async throws {
  100. let builder: Server.Builder
  101. if useTLS {
  102. #if canImport(NIOSSL)
  103. // We're using some self-signed certs here: check they aren't expired.
  104. let caCert = SampleCertificate.ca
  105. let serverCert = SampleCertificate.server
  106. precondition(
  107. !caCert.isExpired && !serverCert.isExpired,
  108. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  109. )
  110. builder = Server.usingTLSBackedByNIOSSL(
  111. on: group,
  112. certificateChain: [serverCert.certificate],
  113. privateKey: SamplePrivateKey.server
  114. )
  115. .withTLS(trustRoots: .certificates([caCert.certificate]))
  116. print("starting secure server")
  117. #else
  118. fatalError("'useTLS: true' passed to \(#function) but NIOSSL is not available")
  119. #endif // canImport(NIOSSL)
  120. } else {
  121. print("starting insecure server")
  122. builder = Server.insecure(group: group)
  123. }
  124. let server = try await builder.withServiceProviders([EchoAsyncProvider()])
  125. .bind(host: "localhost", port: port)
  126. .get()
  127. print("started server: \(server.channel.localAddress!)")
  128. // This blocks to keep the main thread from finishing while the server runs,
  129. // but the server never exits. Kill the process to stop it.
  130. try await server.onClose.get()
  131. }
  132. // MARK: - Client
  133. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  134. func makeClient(
  135. group: EventLoopGroup,
  136. port: Int,
  137. useTLS: Bool,
  138. useInterceptor: Bool
  139. ) -> Echo_EchoAsyncClient {
  140. let builder: ClientConnection.Builder
  141. if useTLS {
  142. #if canImport(NIOSSL)
  143. // We're using some self-signed certs here: check they aren't expired.
  144. let caCert = SampleCertificate.ca
  145. let clientCert = SampleCertificate.client
  146. precondition(
  147. !caCert.isExpired && !clientCert.isExpired,
  148. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  149. )
  150. builder = ClientConnection.usingTLSBackedByNIOSSL(on: group)
  151. .withTLS(certificateChain: [clientCert.certificate])
  152. .withTLS(privateKey: SamplePrivateKey.client)
  153. .withTLS(trustRoots: .certificates([caCert.certificate]))
  154. #else
  155. fatalError("'useTLS: true' passed to \(#function) but NIOSSL is not available")
  156. #endif // canImport(NIOSSL)
  157. } else {
  158. builder = ClientConnection.insecure(group: group)
  159. }
  160. // Start the connection and create the client:
  161. let connection = builder.connect(host: "localhost", port: port)
  162. return Echo_EchoAsyncClient(
  163. channel: connection,
  164. interceptors: useInterceptor ? ExampleClientInterceptorFactory() : nil
  165. )
  166. }
  167. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  168. func callRPC(_ rpc: RPC, using client: Echo_EchoAsyncClient, message: String) async {
  169. do {
  170. switch rpc {
  171. case .get:
  172. try await echoGet(client: client, message: message)
  173. case .collect:
  174. try await echoCollect(client: client, message: message)
  175. case .expand:
  176. try await echoExpand(client: client, message: message)
  177. case .update:
  178. try await echoUpdate(client: client, message: message)
  179. }
  180. } catch {
  181. print("\(rpc) RPC failed: \(error)")
  182. }
  183. }
  184. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  185. func echoGet(client: Echo_EchoAsyncClient, message: String) async throws {
  186. let response = try await client.get(.with { $0.text = message })
  187. print("get received: \(response.text)")
  188. }
  189. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  190. func echoCollect(client: Echo_EchoAsyncClient, message: String) async throws {
  191. let messages = message.components(separatedBy: " ").map { part in
  192. Echo_EchoRequest.with { $0.text = part }
  193. }
  194. let response = try await client.collect(messages)
  195. print("collect received: \(response.text)")
  196. }
  197. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  198. func echoExpand(client: Echo_EchoAsyncClient, message: String) async throws {
  199. for try await response in client.expand((.with { $0.text = message })) {
  200. print("expand received: \(response.text)")
  201. }
  202. }
  203. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  204. func echoUpdate(client: Echo_EchoAsyncClient, message: String) async throws {
  205. let requests = message.components(separatedBy: " ").map { word in
  206. Echo_EchoRequest.with { $0.text = word }
  207. }
  208. for try await response in client.update(requests) {
  209. print("update received: \(response.text)")
  210. }
  211. }
  212. #else
  213. @main
  214. enum Echo {
  215. static func main() {
  216. print("This example requires Swift >= 5.6")
  217. }
  218. }
  219. #endif