main.swift 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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_EchoServiceClient {
  153. // Configure the connection:
  154. var configuration = ClientConnection.Configuration(
  155. target: .hostAndPort(host, port),
  156. eventLoopGroup: group
  157. )
  158. if useTLS {
  159. // We're using some self-signed certs here: check they aren't expired.
  160. let caCert = SampleCertificate.ca
  161. let clientCert = SampleCertificate.client
  162. precondition(
  163. !caCert.isExpired && !clientCert.isExpired,
  164. "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift."
  165. )
  166. configuration.tls = .init(
  167. certificateChain: [.certificate(clientCert.certificate)],
  168. privateKey: .privateKey(SamplePrivateKey.client),
  169. trustRoots: .certificates([caCert.certificate])
  170. )
  171. }
  172. // Start the connection and create the client:
  173. let connection = ClientConnection(configuration: configuration)
  174. return Echo_EchoServiceClient(connection: connection)
  175. }
  176. func callRPC(_ rpc: RPC, using client: Echo_EchoServiceClient, message: String) {
  177. do {
  178. switch rpc {
  179. case .get:
  180. try echoGet(client: client, message: message)
  181. case .collect:
  182. try echoCollect(client: client, message: message)
  183. case .expand:
  184. try echoExpand(client: client, message: message)
  185. case .update:
  186. try echoUpdate(client: client, message: message)
  187. }
  188. } catch {
  189. print("\(rpc) RPC failed: \(error)")
  190. }
  191. }
  192. func echoGet(client: Echo_EchoServiceClient, message: String) throws {
  193. // Get is a unary call.
  194. let get = client.get(.with { $0.text = message })
  195. // Register a callback for the response:
  196. get.response.whenComplete { result in
  197. switch result {
  198. case .success(let response):
  199. print("get receieved: \(response.text)")
  200. case .failure(let error):
  201. print("get failed with error: \(error)")
  202. }
  203. }
  204. // wait() for the call to terminate
  205. let status = try get.status.wait()
  206. print("get completed with status: \(status.code)")
  207. }
  208. func echoCollect(client: Echo_EchoServiceClient, message: String) throws {
  209. // Collect is a client streaming call
  210. let collect = client.collect()
  211. // Split the messages and map them into requests
  212. let messages = message.components(separatedBy: " ").map { part in
  213. Echo_EchoRequest.with { $0.text = part }
  214. }
  215. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  216. collect.sendMessages(messages, promise: nil)
  217. // Close the request stream.
  218. collect.sendEnd(promise: nil)
  219. // Register a callback for the response:
  220. collect.response.whenComplete { result in
  221. switch result {
  222. case .success(let response):
  223. print("collect receieved: \(response.text)")
  224. case .failure(let error):
  225. print("collect failed with error: \(error)")
  226. }
  227. }
  228. // wait() for the call to terminate
  229. let status = try collect.status.wait()
  230. print("collect completed with status: \(status.code)")
  231. }
  232. func echoExpand(client: Echo_EchoServiceClient, message: String) throws {
  233. // Expand is a server streaming call; provide a response handler.
  234. let expand = client.expand(.with { $0.text = message}) { response in
  235. print("expand received: \(response.text)")
  236. }
  237. // wait() for the call to terminate
  238. let status = try expand.status.wait()
  239. print("expand completed with status: \(status.code)")
  240. }
  241. func echoUpdate(client: Echo_EchoServiceClient, message: String) throws {
  242. // Update is a bidirectional streaming call; provide a response handler.
  243. let update = client.update { response in
  244. print("update received: \(response.text)")
  245. }
  246. // Split the messages and map them into requests
  247. let messages = message.components(separatedBy: " ").map { part in
  248. Echo_EchoRequest.with { $0.text = part }
  249. }
  250. // Stream the to the service (this can also be done on individual requests using `sendMessage`).
  251. update.sendMessages(messages, promise: nil)
  252. // Close the request stream.
  253. update.sendEnd(promise: nil)
  254. // wait() for the call to terminate
  255. let status = try update.status.wait()
  256. print("update completed with status: \(status.code)")
  257. }
  258. main(args: CommandLine.arguments)