main.swift 7.5 KB

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