main.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  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 ArgumentParser
  17. import EchoImplementation
  18. import EchoModel
  19. import GRPC
  20. import GRPCSampleData
  21. import Logging
  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. struct Echo: ParsableCommand {
  35. static var configuration = CommandConfiguration(
  36. abstract: "An example to run and call a simple gRPC service for echoing messages.",
  37. subcommands: [Server.self, Client.self]
  38. )
  39. struct Server: ParsableCommand {
  40. static var configuration = CommandConfiguration(
  41. abstract: "Start a gRPC server providing the Echo service."
  42. )
  43. @Option(help: "The port to listen on for new connections")
  44. var port = 1234
  45. @Flag(help: "Whether TLS should be used or not")
  46. var tls = false
  47. func run() throws {
  48. let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  49. defer {
  50. try! group.syncShutdownGracefully()
  51. }
  52. do {
  53. try startEchoServer(group: group, port: self.port, useTLS: self.tls)
  54. } catch {
  55. print("Error running server: \(error)")
  56. }
  57. }
  58. }
  59. struct Client: ParsableCommand {
  60. static var configuration = CommandConfiguration(
  61. abstract: "Calls an RPC on the Echo server."
  62. )
  63. @Option(help: "The port to connect to")
  64. var port = 1234
  65. @Flag(help: "Whether TLS should be used or not")
  66. var tls = false
  67. @Flag(help: "Whether interceptors should be used, see 'docs/interceptors-tutorial.md'.")
  68. var intercept = false
  69. @Option(help: "RPC to call ('get', 'collect', 'expand', 'update').")
  70. var rpc: RPC = .get
  71. @Option(help: "How many RPCs to do.")
  72. var iterations: Int = 1
  73. @Argument(help: "Message to echo")
  74. var message: String
  75. func run() throws {
  76. let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  77. defer {
  78. try! group.syncShutdownGracefully()
  79. }
  80. let client = try makeClient(
  81. group: group,
  82. port: self.port,
  83. useTLS: self.tls,
  84. useInterceptor: self.intercept
  85. )
  86. defer {
  87. try! client.channel.close().wait()
  88. }
  89. for _ in 0 ..< self.iterations {
  90. callRPC(self.rpc, using: client, message: self.message)
  91. }
  92. }
  93. }
  94. }
  95. // MARK: - Server / Client
  96. func startEchoServer(group: EventLoopGroup, port: Int, useTLS: Bool) throws {
  97. let builder: Server.Builder
  98. if useTLS {
  99. #if canImport(NIOSSL)
  100. // We're using some self-signed certs here: check they aren't expired.
  101. let caCert = SampleCertificate.ca
  102. let serverCert = SampleCertificate.server
  103. precondition(
  104. !caCert.isExpired && !serverCert.isExpired,
  105. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  106. )
  107. builder = Server.usingTLSBackedByNIOSSL(
  108. on: group,
  109. certificateChain: [serverCert.certificate],
  110. privateKey: SamplePrivateKey.server
  111. )
  112. .withTLS(trustRoots: .certificates([caCert.certificate]))
  113. print("starting secure server")
  114. #else
  115. fatalError("'useTLS: true' passed to \(#function) but NIOSSL is not available")
  116. #endif // canImport(NIOSSL)
  117. } else {
  118. print("starting insecure server")
  119. builder = Server.insecure(group: group)
  120. }
  121. let server = try builder.withServiceProviders([EchoProvider()])
  122. .bind(host: "localhost", port: port)
  123. .wait()
  124. print("started server: \(server.channel.localAddress!)")
  125. // This blocks to keep the main thread from finishing while the server runs,
  126. // but the server never exits. Kill the process to stop it.
  127. try server.onClose.wait()
  128. }
  129. func makeClient(
  130. group: EventLoopGroup,
  131. port: Int,
  132. useTLS: Bool,
  133. useInterceptor: Bool
  134. ) throws -> Echo_EchoClient {
  135. let security: GRPCChannelPool.Configuration.TransportSecurity
  136. if useTLS {
  137. #if canImport(NIOSSL)
  138. // We're using some self-signed certs here: check they aren't expired.
  139. let caCert = SampleCertificate.ca
  140. let clientCert = SampleCertificate.client
  141. precondition(
  142. !caCert.isExpired && !clientCert.isExpired,
  143. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  144. )
  145. let tlsConfiguration = GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  146. certificateChain: [.certificate(clientCert.certificate)],
  147. privateKey: .privateKey(SamplePrivateKey.client),
  148. trustRoots: .certificates([caCert.certificate])
  149. )
  150. security = .tls(tlsConfiguration)
  151. #else
  152. fatalError("'useTLS: true' passed to \(#function) but NIOSSL is not available")
  153. #endif // canImport(NIOSSL)
  154. } else {
  155. security = .plaintext
  156. }
  157. let channel = try GRPCChannelPool.with(
  158. target: .host("localhost", port: port),
  159. transportSecurity: security,
  160. eventLoopGroup: group
  161. )
  162. return Echo_EchoClient(
  163. channel: channel,
  164. interceptors: useInterceptor ? ExampleClientInterceptorFactory() : nil
  165. )
  166. }
  167. func callRPC(_ rpc: RPC, using client: Echo_EchoClient, message: String) {
  168. do {
  169. switch rpc {
  170. case .get:
  171. try echoGet(client: client, message: message)
  172. case .collect:
  173. try echoCollect(client: client, message: message)
  174. case .expand:
  175. try echoExpand(client: client, message: message)
  176. case .update:
  177. try echoUpdate(client: client, message: message)
  178. }
  179. } catch {
  180. print("\(rpc) RPC failed: \(error)")
  181. }
  182. }
  183. func echoGet(client: Echo_EchoClient, message: String) throws {
  184. // Get is a unary call.
  185. let get = client.get(.with { $0.text = message })
  186. // Register a callback for the response:
  187. get.response.whenComplete { result in
  188. switch result {
  189. case let .success(response):
  190. print("get receieved: \(response.text)")
  191. case let .failure(error):
  192. print("get failed with error: \(error)")
  193. }
  194. }
  195. // wait() for the call to terminate
  196. let status = try get.status.wait()
  197. print("get completed with status: \(status.code)")
  198. }
  199. func echoCollect(client: Echo_EchoClient, message: String) throws {
  200. // Collect is a client streaming call
  201. let collect = client.collect()
  202. // Split the messages and map them into requests
  203. let messages = message.components(separatedBy: " ").map { part in
  204. Echo_EchoRequest.with { $0.text = part }
  205. }
  206. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  207. collect.sendMessages(messages, promise: nil)
  208. // Close the request stream.
  209. collect.sendEnd(promise: nil)
  210. // Register a callback for the response:
  211. collect.response.whenComplete { result in
  212. switch result {
  213. case let .success(response):
  214. print("collect receieved: \(response.text)")
  215. case let .failure(error):
  216. print("collect failed with error: \(error)")
  217. }
  218. }
  219. // wait() for the call to terminate
  220. let status = try collect.status.wait()
  221. print("collect completed with status: \(status.code)")
  222. }
  223. func echoExpand(client: Echo_EchoClient, message: String) throws {
  224. // Expand is a server streaming call; provide a response handler.
  225. let expand = client.expand(.with { $0.text = message }) { response in
  226. print("expand received: \(response.text)")
  227. }
  228. // wait() for the call to terminate
  229. let status = try expand.status.wait()
  230. print("expand completed with status: \(status.code)")
  231. }
  232. func echoUpdate(client: Echo_EchoClient, message: String) throws {
  233. // Update is a bidirectional streaming call; provide a response handler.
  234. let update = client.update { response in
  235. print("update received: \(response.text)")
  236. }
  237. // Split the messages and map them into requests
  238. let messages = message.components(separatedBy: " ").map { part in
  239. Echo_EchoRequest.with { $0.text = part }
  240. }
  241. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  242. update.sendMessages(messages, promise: nil)
  243. // Close the request stream.
  244. update.sendEnd(promise: nil)
  245. // wait() for the call to terminate
  246. let status = try update.status.wait()
  247. print("update completed with status: \(status.code)")
  248. }
  249. Echo.main()