Parcourir la source

Update PCAP example to be async (#1422)

Motivation:

Our examples should at least attempt to be up-to-date. The PCAP example
currenly uses the NIO based API, we should switch it to the async API.

Modifications:

- Update the PCAP example to use async/await
- Ditch logging for print

Result:

PCAP example is async/await
George Barnett il y a 3 ans
Parent
commit
d3f5f64088

+ 0 - 1
Package.swift

@@ -409,7 +409,6 @@ extension Target {
       .nioCore,
       .nioPosix,
       .nioExtras,
-      .logging,
       .argumentParser,
     ],
     path: "Sources/Examples/PacketCapture",

+ 17 - 0
Sources/Examples/PacketCapture/Empty.swift

@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file exists to workaround https://github.com/apple/swift/issues/55127.

+ 94 - 0
Sources/Examples/PacketCapture/PacketCapture.swift

@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#if compiler(>=5.6)
+import ArgumentParser
+import EchoModel
+import GRPC
+import NIOCore
+import NIOExtras
+import NIOPosix
+
+@main
+@available(macOS 10.15, *)
+struct PCAP: AsyncParsableCommand {
+  @Option(help: "The port to connect to")
+  var port = 1234
+
+  func run() async throws {
+    // Create an `EventLoopGroup`.
+    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+    defer {
+      try! group.syncShutdownGracefully()
+    }
+
+    // The filename for the .pcap file to write to.
+    let path = "packet-capture-example.pcap"
+    let fileSink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(
+      path: path
+    ) { error in
+      print("Failed to write with error '\(error)' for path '\(path)'")
+    }
+
+    // Ensure that we close the file sink when we're done with it.
+    defer {
+      try! fileSink.syncClose()
+    }
+
+    let channel = try GRPCChannelPool.with(
+      target: .host("localhost", port: self.port),
+      transportSecurity: .plaintext,
+      eventLoopGroup: group
+    ) {
+      $0.debugChannelInitializer = { channel in
+        // Create the PCAP handler and add it to the start of the channel pipeline. If this example
+        // used TLS we would likely want to place the handler in a different position in the
+        // pipeline so that the captured packets in the trace would not be encrypted.
+        let writePCAPHandler = NIOWritePCAPHandler(mode: .client, fileSink: fileSink.write(buffer:))
+        return channel.eventLoop.makeCompletedFuture(Result {
+          try channel.pipeline.syncOperations.addHandler(writePCAPHandler, position: .first)
+        })
+      }
+    }
+
+    // Create a client.
+    let echo = Echo_EchoAsyncClient(channel: channel)
+
+    let messages = ["foo", "bar", "baz", "thud", "grunt", "gorp"].map { text in
+      Echo_EchoRequest.with { $0.text = text }
+    }
+
+    do {
+      for try await response in echo.update(messages) {
+        print("Received response '\(response.text)'")
+      }
+      print("RPC completed successfully")
+    } catch {
+      print("RPC failed with error '\(error)'")
+    }
+
+    print("Try opening '\(path)' in Wireshark or with 'tcpdump -r \(path)'")
+
+    try await echo.channel.close().get()
+  }
+}
+#else
+@main
+enum PCAP {
+  static func main() {
+    print("This example requires Swift >= 5.6")
+  }
+}
+#endif // compiler(>=5.6)

+ 1 - 11
Sources/Examples/PacketCapture/README.md

@@ -26,17 +26,7 @@ In a separate shell run:
 $ swift run PacketCapture
 ```
 
-Some logs should be emitted similar to below, including the path of the
-*.pcap* file:
-
-```sh
-2020-07-24T10:48:50+0100 info gRPC PCAP Demo : Creating fileSink for path './channel-ObjectIdentifier(0x00007f8a25604c40).pcap'
-2020-07-24T10:48:50+0100 info gRPC PCAP Demo : ✅ Successfully created fileSink for path './channel-ObjectIdentifier(0x00007f8a25604c40).pcap'
-...
-2020-07-24T10:48:50+0100 info gRPC PCAP Demo : ✅ RPC completed successfully
-...
-2020-07-24T10:48:50+0100 info gRPC PCAP Demo : Done!
-```
+The pcap file will be written to 'packet-capture-example.pcap'.
 
 The *.pcap* file can be opened with either: [Wireshark][wireshark] or `tcpdump
 -r <PCAP_FILE>`.

+ 0 - 146
Sources/Examples/PacketCapture/main.swift

@@ -1,146 +0,0 @@
-/*
- * Copyright 2020, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#if compiler(>=5.6)
-import ArgumentParser
-import Dispatch
-import EchoModel
-import GRPC
-import Logging
-import NIOCore
-import NIOExtras
-import NIOPosix
-
-// Create a logger.
-let logger = Logger(label: "gRPC PCAP Demo")
-
-// Closing file sinks is blocking, it therefore can't be done on an EventLoop.
-let fileSinkCloseQueue = DispatchQueue(label: "io.grpc")
-let fileSinkCloseGroup = DispatchGroup()
-defer {
-  // Make sure we wait for all file sinks to be closed before we exit.
-  fileSinkCloseGroup.wait()
-  logger.info("Done!")
-}
-
-/// Adds a `NIOWritePCAPHandler` to the given channel.
-///
-/// A file sink will also be created to write the PCAP to `./channel-{ID}.pcap` where `{ID}` is
-/// an identifier created from the given `channel`. The file sink will be closed when the channel
-/// closes and will notify the `fileSinkCloseGroup` when it has been closed.
-///
-/// - Parameter channel: The channel to add the PCAP handler to.
-/// - Returns: An `EventLoopFuture` indicating whether the PCAP handler was successfully added.
-@Sendable
-func addPCAPHandler(toChannel channel: Channel) -> EventLoopFuture<Void> {
-  // The debug initializer can be called multiple times. We'll use the object ID of the channel
-  // to disambiguate between the files.
-  let channelID = ObjectIdentifier(channel)
-  let path = "./channel-\(channelID).pcap"
-
-  logger.info("Creating fileSink for path '\(path)'")
-
-  do {
-    // Create a file sink.
-    let fileSink = try NIOWritePCAPHandler.SynchronizedFileSink
-      .fileSinkWritingToFile(path: path) { error in
-        logger.error("💥 Failed to write with error '\(error)' for path '\(path)'")
-      }
-
-    logger.info("✅ Successfully created fileSink for path '\(path)'")
-
-    // We need to close the file sink when we're done. It can't be closed from the event loop so
-    // we'll use a dispatch queue instead.
-    fileSinkCloseGroup.enter()
-    channel.closeFuture.whenComplete { _ in
-      fileSinkCloseQueue.async {
-        do {
-          try fileSink.syncClose()
-        } catch {
-          logger.error("💥 Failed to close fileSink with error '\(error)' for path '\(path)'")
-        }
-      }
-      fileSinkCloseGroup.leave()
-    }
-
-    // Add the handler to the pipeline.
-    let handler = NIOWritePCAPHandler(mode: .client, fileSink: fileSink.write(buffer:))
-    // We're not using TLS in this example so ".first" is the right place.
-    return channel.pipeline.addHandler(handler, position: .first)
-  } catch {
-    logger.error("💥 Failed to create fileSink with error '\(error)' for path '\(path)'")
-    return channel.eventLoop.makeFailedFuture(error)
-  }
-}
-
-struct PCAP: ParsableCommand {
-  @Option(help: "The port to connect to")
-  var port = 1234
-
-  func run() throws {
-    // Create an `EventLoopGroup`.
-    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
-    defer {
-      try! group.syncShutdownGracefully()
-    }
-
-    // Create a channel.
-    let channel = ClientConnection.insecure(group: group)
-      // Set the debug initializer: it will add a handler to each created channel to write a PCAP when
-      // the channel is closed.
-      .withDebugChannelInitializer(addPCAPHandler(toChannel:))
-      // We're connecting to our own server here; we'll disable connection re-establishment.
-      .withConnectionReestablishment(enabled: false)
-      // Connect!
-      .connect(host: "localhost", port: self.port)
-
-    // Create a client.
-    let echo = Echo_EchoNIOClient(channel: channel)
-
-    // Start an RPC.
-    let update = echo.update { response in
-      logger.info("Received response '\(response.text)'")
-    }
-
-    // Send some requests.
-    for text in ["foo", "bar", "baz", "thud", "grunt", "gorp"] {
-      update.sendMessage(.with { $0.text = text }).whenSuccess {
-        logger.info("Sent request '\(text)'")
-      }
-    }
-
-    // Close the request stream.
-    update.sendEnd(promise: nil)
-
-    // Once the RPC finishes close the connection.
-    let closed = update.status.flatMap { status -> EventLoopFuture<Void> in
-      if status.isOk {
-        logger.info("✅ RPC completed successfully")
-      } else {
-        logger.error("💥 RPC failed with status '\(status)'")
-      }
-      logger.info("Closing channel")
-      return channel.close()
-    }
-
-    // Wait for the channel to be closed.
-    try closed.wait()
-  }
-}
-
-PCAP.main()
-#else
-fatalError("This example requires Swift 5.6.")
-#endif // compiler(>=5.6)