swift-server / async-http-client

HTTP client library built on SwiftNIO
https://swiftpackageindex.com/swift-server/async-http-client/main/documentation/asynchttpclient
Apache License 2.0
907 stars 116 forks source link

for debugging purposes, we should offer an option to switch on plain text .pcap recording #239

Open weissi opened 4 years ago

weissi commented 4 years ago

for debugging purposes, we should offer an option to switch on plain text .pcap recording.

It should be very clear that it's for debugging because NIOWritePCAPHandler.SynchronisedFileSink blocks the event loop (we could write an async file sink ofc)...

A pretty terrible patch that can serve as example (but not the final impl ofc) is here:

diff --git a/Package.swift b/Package.swift
index bce6b69..0575c2d 100644
--- a/Package.swift
+++ b/Package.swift
@@ -31,7 +31,7 @@ let package = Package(
         .target(
             name: "AsyncHTTPClient",
             dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression",
-                           "NIOFoundationCompat", "NIOTransportServices", "Logging"]
+                           "NIOFoundationCompat", "NIOTransportServices", "Logging", "NIOExtras"]
         ),
         .testTarget(
             name: "AsyncHTTPClientTests",
diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift
index 46e0b61..03abfc3 100644
--- a/Sources/AsyncHTTPClient/ConnectionPool.swift
+++ b/Sources/AsyncHTTPClient/ConnectionPool.swift
@@ -20,6 +20,7 @@ import NIOHTTP1
 import NIOHTTPCompression
 import NIOTLS
 import NIOTransportServices
+import NIOExtras

 /// A connection pool that manages and creates new connections to hosts respecting the specified preferences
 ///
@@ -257,6 +258,10 @@ struct ConnectionKey: Hashable {
     }
 }

+let fileSink = try? NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: "/tmp/ahc-\(getpid()).pcap") { error in
+    print("AHC ERROR: something went wrong creating the file sink: \(error)")
+}
+
 /// A connection provider of `HTTP/1.1` connections with a given `Key` (host, scheme, port)
 ///
 /// On top of enabling connection reuse this provider it also facilitates the creation
@@ -529,9 +534,22 @@ class HTTP1ConnectionProvider {
             let requiresSSLHandler = self.configuration.proxy != nil && self.key.scheme.requiresTLS
             let handshakePromise = channel.eventLoop.makePromise(of: Void.self)

+
             channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise)

+            func addWritePCAP() -> EventLoopFuture<Void> {
+                if let fileSink = fileSink {
+                    return channel.pipeline.addHandler(NIOWritePCAPHandler(mode: .client, fileSink: fileSink.write(buffer:)),
+                                                position: .last)
+                } else {
+                    return channel.eventLoop.makeSucceededFuture(())
+                }
+
+            }
+
             return handshakePromise.futureResult.flatMap {
+                addWritePCAP()
+            }.flatMap {
                 channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes)
             }.flatMap {
                 #if canImport(Network)
weissi commented 1 year ago

more hacky but newer patch for this

diff --git a/Package.swift b/Package.swift
index f72de84..d572ea0 100644
--- a/Package.swift
+++ b/Package.swift
@@ -39,6 +39,7 @@ let package = Package(
                 .product(name: "NIO", package: "swift-nio"),
                 .product(name: "NIOCore", package: "swift-nio"),
                 .product(name: "NIOPosix", package: "swift-nio"),
+                .product(name: "NIOExtras", package: "swift-nio-extras"),
                 .product(name: "NIOHTTP1", package: "swift-nio"),
                 .product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
                 .product(name: "NIOHTTP2", package: "swift-nio-http2"),
diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift
index 1444df9..bd57a18 100644
--- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift
+++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift
@@ -20,6 +20,8 @@ import NIOPosix
 import NIOSOCKS
 import NIOSSL
 import NIOTLS
+import NIOExtras
+import Foundation
 #if canImport(Network)
 import NIOTransportServices
 #endif
@@ -448,6 +450,17 @@ extension HTTPConnectionPool.ConnectionFactory {
                     .tlsOptions(options)
                     .channelInitializer { channel in
                         do {
+                            let fileSink = try! NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: "/tmp/packets-nw-\(sslServerHostname ?? "unknown")-\(UUID())-\(getpid())-\(Date()).pcap",
+                                                                                                               errorHandler: { error in
+                                logger.error("ouch: \(error)")
+                            })
+
+                            try sync.addHandler(NIOWritePCAPHandler(mode: .client,
+                                                                    fileSink: fileSink.write(buffer:)))
+                            channel.closeFuture.whenComplete { _ in
+                                try! fileSink.syncClose()
+                            }
+
                             try channel.pipeline.syncOperations.addHandler(HTTPClient.NWErrorHandler())
                             try channel.pipeline.syncOperations.addHandler(NWWaitingHandler(requester: requester, connectionID: connectionID))
                             // we don't need to set a TLS deadline for NIOTS connections, since the
@@ -485,6 +498,16 @@ extension HTTPConnectionPool.ConnectionFactory {

                         try sync.addHandler(sslHandler)
                         try sync.addHandler(tlsEventHandler)
+                        let fileSink = try! NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: "/tmp/packets-\(sslServerHostname ?? "unknown")-\(UUID())-\(getpid())-\(Date()).pcap",
+                                                                                                           errorHandler: { error in
+                            logger.error("ouch: \(error)")
+                        })
+
+                        try sync.addHandler(NIOWritePCAPHandler(mode: .client,
+                                                                fileSink: fileSink.write(buffer:)))
+                        channel.closeFuture.whenComplete { _ in
+                            try! fileSink.syncClose()
+                        }
                         return channel.eventLoop.makeSucceededVoidFuture()
                     } catch {
                         return channel.eventLoop.makeFailedFuture(error)