main.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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 EchoImplementation
  17. import EchoModel
  18. import Foundation
  19. import GRPC
  20. import GRPCSampleData
  21. import Logging
  22. import NIO
  23. import NIOSSL
  24. // MARK: - Argument parsing
  25. enum RPC: String {
  26. case get
  27. case collect
  28. case expand
  29. case update
  30. }
  31. enum Command {
  32. case server(port: Int, useTLS: Bool)
  33. case client(host: String, port: Int, useTLS: Bool, rpc: RPC, message: String)
  34. init?(from args: [String]) {
  35. guard !args.isEmpty else {
  36. return nil
  37. }
  38. var args = args
  39. switch args.removeFirst() {
  40. case "server":
  41. guard args.count == 1 || args.count == 2,
  42. let port = args.popLast().flatMap(Int.init),
  43. let useTLS = Command.parseTLSArg(args.popLast())
  44. else {
  45. return nil
  46. }
  47. self = .server(port: port, useTLS: useTLS)
  48. case "client":
  49. guard args.count == 4 || args.count == 5,
  50. let message = args.popLast(),
  51. let rpc = args.popLast().flatMap(RPC.init),
  52. let port = args.popLast().flatMap(Int.init),
  53. let host = args.popLast(),
  54. let useTLS = Command.parseTLSArg(args.popLast())
  55. else {
  56. return nil
  57. }
  58. self = .client(host: host, port: port, useTLS: useTLS, rpc: rpc, message: message)
  59. default:
  60. return nil
  61. }
  62. }
  63. private static func parseTLSArg(_ arg: String?) -> Bool? {
  64. switch arg {
  65. case .some("--tls"):
  66. return true
  67. case .none, .some("--notls"):
  68. return false
  69. default:
  70. return nil
  71. }
  72. }
  73. }
  74. func printUsageAndExit(program: String) -> Never {
  75. print("""
  76. Usage: \(program) COMMAND [OPTIONS...]
  77. Commands:
  78. server [--tls|--notls] PORT Starts the echo server on the given port.
  79. client [--tls|--notls] HOST PORT RPC MESSAGE Connects to the echo server on the given host
  80. host and port and calls the RPC with the
  81. provided message. See below for a list of
  82. possible RPCs.
  83. RPCs:
  84. * get (unary)
  85. * collect (client streaming)
  86. * expand (server streaming)
  87. * update (bidirectional streaming)
  88. """)
  89. exit(1)
  90. }
  91. func main(args: [String]) {
  92. var args = args
  93. let program = args.removeFirst()
  94. guard let command = Command(from: args) else {
  95. printUsageAndExit(program: program)
  96. }
  97. // Reduce the logging verbosity.
  98. LoggingSystem.bootstrap {
  99. var handler = StreamLogHandler.standardOutput(label: $0)
  100. handler.logLevel = .warning
  101. return handler
  102. }
  103. // Okay, we're nearly ready to start, create an `EventLoopGroup` most suitable for our platform.
  104. let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
  105. defer {
  106. try! group.syncShutdownGracefully()
  107. }
  108. // Now run the server/client.
  109. switch command {
  110. case let .server(port: port, useTLS: useTLS):
  111. do {
  112. try startEchoServer(group: group, port: port, useTLS: useTLS)
  113. } catch {
  114. print("Error running server: \(error)")
  115. }
  116. case let .client(host: host, port: port, useTLS: useTLS, rpc: rpc, message: message):
  117. let client = makeClient(group: group, host: host, port: port, useTLS: useTLS)
  118. callRPC(rpc, using: client, message: message)
  119. }
  120. }
  121. // MARK: - Server / Client
  122. func startEchoServer(group: EventLoopGroup, port: Int, useTLS: Bool) throws {
  123. let builder: Server.Builder
  124. if useTLS {
  125. // We're using some self-signed certs here: check they aren't expired.
  126. let caCert = SampleCertificate.ca
  127. let serverCert = SampleCertificate.server
  128. precondition(
  129. !caCert.isExpired && !serverCert.isExpired,
  130. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  131. )
  132. builder = Server.secure(
  133. group: group,
  134. certificateChain: [serverCert.certificate],
  135. privateKey: SamplePrivateKey.server
  136. )
  137. .withTLS(trustRoots: .certificates([caCert.certificate]))
  138. print("starting secure server")
  139. } else {
  140. print("starting insecure server")
  141. builder = Server.insecure(group: group)
  142. }
  143. let server = try builder.withServiceProviders([EchoProvider()])
  144. .bind(host: "localhost", port: port)
  145. .wait()
  146. print("started server: \(server.channel.localAddress!)")
  147. // This blocks to keep the main thread from finishing while the server runs,
  148. // but the server never exits. Kill the process to stop it.
  149. try server.onClose.wait()
  150. }
  151. func makeClient(group: EventLoopGroup, host: String, port: Int, useTLS: Bool) -> Echo_EchoClient {
  152. let builder: ClientConnection.Builder
  153. if useTLS {
  154. // We're using some self-signed certs here: check they aren't expired.
  155. let caCert = SampleCertificate.ca
  156. let clientCert = SampleCertificate.client
  157. precondition(
  158. !caCert.isExpired && !clientCert.isExpired,
  159. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  160. )
  161. builder = ClientConnection.secure(group: group)
  162. .withTLS(certificateChain: [clientCert.certificate])
  163. .withTLS(privateKey: SamplePrivateKey.client)
  164. .withTLS(trustRoots: .certificates([caCert.certificate]))
  165. } else {
  166. builder = ClientConnection.insecure(group: group)
  167. }
  168. // Start the connection and create the client:
  169. let connection = builder.connect(host: host, port: port)
  170. return Echo_EchoClient(channel: connection)
  171. }
  172. func callRPC(_ rpc: RPC, using client: Echo_EchoClient, message: String) {
  173. do {
  174. switch rpc {
  175. case .get:
  176. try echoGet(client: client, message: message)
  177. case .collect:
  178. try echoCollect(client: client, message: message)
  179. case .expand:
  180. try echoExpand(client: client, message: message)
  181. case .update:
  182. try echoUpdate(client: client, message: message)
  183. }
  184. } catch {
  185. print("\(rpc) RPC failed: \(error)")
  186. }
  187. }
  188. func echoGet(client: Echo_EchoClient, message: String) throws {
  189. // Get is a unary call.
  190. let get = client.get(.with { $0.text = message })
  191. // Register a callback for the response:
  192. get.response.whenComplete { result in
  193. switch result {
  194. case let .success(response):
  195. print("get receieved: \(response.text)")
  196. case let .failure(error):
  197. print("get failed with error: \(error)")
  198. }
  199. }
  200. // wait() for the call to terminate
  201. let status = try get.status.wait()
  202. print("get completed with status: \(status.code)")
  203. }
  204. func echoCollect(client: Echo_EchoClient, message: String) throws {
  205. // Collect is a client streaming call
  206. let collect = client.collect()
  207. // Split the messages and map them into requests
  208. let messages = message.components(separatedBy: " ").map { part in
  209. Echo_EchoRequest.with { $0.text = part }
  210. }
  211. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  212. collect.sendMessages(messages, promise: nil)
  213. // Close the request stream.
  214. collect.sendEnd(promise: nil)
  215. // Register a callback for the response:
  216. collect.response.whenComplete { result in
  217. switch result {
  218. case let .success(response):
  219. print("collect receieved: \(response.text)")
  220. case let .failure(error):
  221. print("collect failed with error: \(error)")
  222. }
  223. }
  224. // wait() for the call to terminate
  225. let status = try collect.status.wait()
  226. print("collect completed with status: \(status.code)")
  227. }
  228. func echoExpand(client: Echo_EchoClient, message: String) throws {
  229. // Expand is a server streaming call; provide a response handler.
  230. let expand = client.expand(.with { $0.text = message }) { response in
  231. print("expand received: \(response.text)")
  232. }
  233. // wait() for the call to terminate
  234. let status = try expand.status.wait()
  235. print("expand completed with status: \(status.code)")
  236. }
  237. func echoUpdate(client: Echo_EchoClient, message: String) throws {
  238. // Update is a bidirectional streaming call; provide a response handler.
  239. let update = client.update { response in
  240. print("update received: \(response.text)")
  241. }
  242. // Split the messages and map them into requests
  243. let messages = message.components(separatedBy: " ").map { part in
  244. Echo_EchoRequest.with { $0.text = part }
  245. }
  246. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  247. update.sendMessages(messages, promise: nil)
  248. // Close the request stream.
  249. update.sendEnd(promise: nil)
  250. // wait() for the call to terminate
  251. let status = try update.status.wait()
  252. print("update completed with status: \(status.code)")
  253. }
  254. main(args: CommandLine.arguments)