main.swift 9.0 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 Foundation
  17. import NIO
  18. import NIOSSL
  19. import GRPC
  20. import GRPCSampleData
  21. import EchoImplementation
  22. import EchoModel
  23. import Logging
  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. // Configure the server:
  124. var configuration = Server.Configuration(
  125. target: .hostAndPort("localhost", port),
  126. eventLoopGroup: group,
  127. serviceProviders: [EchoProvider()]
  128. )
  129. if useTLS {
  130. // We're using some self-signed certs here: check they aren't expired.
  131. let caCert = SampleCertificate.ca
  132. let serverCert = SampleCertificate.server
  133. precondition(
  134. !caCert.isExpired && !serverCert.isExpired,
  135. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  136. )
  137. configuration.tls = .init(
  138. certificateChain: [.certificate(serverCert.certificate)],
  139. privateKey: .privateKey(SamplePrivateKey.server),
  140. trustRoots: .certificates([caCert.certificate])
  141. )
  142. print("starting secure server")
  143. } else {
  144. print("starting insecure server")
  145. }
  146. let server = try Server.start(configuration: configuration).wait()
  147. print("started server: \(server.channel.localAddress!)")
  148. // This blocks to keep the main thread from finishing while the server runs,
  149. // but the server never exits. Kill the process to stop it.
  150. try server.onClose.wait()
  151. }
  152. func makeClient(group: EventLoopGroup, host: String, port: Int, useTLS: Bool) -> Echo_EchoClient {
  153. let builder: ClientConnection.Builder
  154. if useTLS {
  155. // We're using some self-signed certs here: check they aren't expired.
  156. let caCert = SampleCertificate.ca
  157. let clientCert = SampleCertificate.client
  158. precondition(
  159. !caCert.isExpired && !clientCert.isExpired,
  160. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  161. )
  162. builder = ClientConnection.secure(group: group)
  163. .withTLS(certificateChain: [clientCert.certificate])
  164. .withTLS(privateKey: SamplePrivateKey.client)
  165. .withTLS(trustRoots: .certificates([caCert.certificate]))
  166. } else {
  167. builder = ClientConnection.insecure(group: group)
  168. }
  169. // Start the connection and create the client:
  170. let connection = builder.connect(host: host, port: port)
  171. return Echo_EchoClient(channel: connection)
  172. }
  173. func callRPC(_ rpc: RPC, using client: Echo_EchoClient, message: String) {
  174. do {
  175. switch rpc {
  176. case .get:
  177. try echoGet(client: client, message: message)
  178. case .collect:
  179. try echoCollect(client: client, message: message)
  180. case .expand:
  181. try echoExpand(client: client, message: message)
  182. case .update:
  183. try echoUpdate(client: client, message: message)
  184. }
  185. } catch {
  186. print("\(rpc) RPC failed: \(error)")
  187. }
  188. }
  189. func echoGet(client: Echo_EchoClient, message: String) throws {
  190. // Get is a unary call.
  191. let get = client.get(.with { $0.text = message })
  192. // Register a callback for the response:
  193. get.response.whenComplete { result in
  194. switch result {
  195. case .success(let response):
  196. print("get receieved: \(response.text)")
  197. case .failure(let error):
  198. print("get failed with error: \(error)")
  199. }
  200. }
  201. // wait() for the call to terminate
  202. let status = try get.status.wait()
  203. print("get completed with status: \(status.code)")
  204. }
  205. func echoCollect(client: Echo_EchoClient, message: String) throws {
  206. // Collect is a client streaming call
  207. let collect = client.collect()
  208. // Split the messages and map them into requests
  209. let messages = message.components(separatedBy: " ").map { part in
  210. Echo_EchoRequest.with { $0.text = part }
  211. }
  212. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  213. collect.sendMessages(messages, promise: nil)
  214. // Close the request stream.
  215. collect.sendEnd(promise: nil)
  216. // Register a callback for the response:
  217. collect.response.whenComplete { result in
  218. switch result {
  219. case .success(let response):
  220. print("collect receieved: \(response.text)")
  221. case .failure(let error):
  222. print("collect failed with error: \(error)")
  223. }
  224. }
  225. // wait() for the call to terminate
  226. let status = try collect.status.wait()
  227. print("collect completed with status: \(status.code)")
  228. }
  229. func echoExpand(client: Echo_EchoClient, message: String) throws {
  230. // Expand is a server streaming call; provide a response handler.
  231. let expand = client.expand(.with { $0.text = message}) { response in
  232. print("expand received: \(response.text)")
  233. }
  234. // wait() for the call to terminate
  235. let status = try expand.status.wait()
  236. print("expand completed with status: \(status.code)")
  237. }
  238. func echoUpdate(client: Echo_EchoClient, message: String) throws {
  239. // Update is a bidirectional streaming call; provide a response handler.
  240. let update = client.update { response in
  241. print("update received: \(response.text)")
  242. }
  243. // Split the messages and map them into requests
  244. let messages = message.components(separatedBy: " ").map { part in
  245. Echo_EchoRequest.with { $0.text = part }
  246. }
  247. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  248. update.sendMessages(messages, promise: nil)
  249. // Close the request stream.
  250. update.sendEnd(promise: nil)
  251. // wait() for the call to terminate
  252. let status = try update.status.wait()
  253. print("update completed with status: \(status.code)")
  254. }
  255. main(args: CommandLine.arguments)