11// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
22// SPDX-License-Identifier: Apache-2.0
33
4+ //! File-based HTTP request dumping for testing and debugging.
5+ //!
6+ //! This module implements a workaround to intercept and dump HTTP requests made by reqwest
7+ //! to a local file. It works by spawning a local server (Unix domain socket on Unix,
8+ //! named pipe on Windows) that captures the raw HTTP bytes before writing them to disk.
9+ //!
10+ //! This is primarily used for testing to validate the exact bytes sent over the wire.
11+ //!
12+ //! # Future
13+ //!
14+ //! This module exists as a workaround and will hopefully be replaced once reqwest adds
15+ //! native support for file output: <https://github.com/seanmonstar/reqwest/issues/2883>
16+
417#[ cfg( any( unix, windows) ) ]
518use std:: path:: PathBuf ;
619
@@ -64,13 +77,22 @@ pub(crate) fn spawn_dump_server(output_path: PathBuf) -> anyhow::Result<PathBuf>
6477
6578/// Async server loop for Windows named pipes
6679#[ cfg( windows) ]
67- async fn run_dump_server_windows ( output_path : PathBuf , pipe_name : String ) -> anyhow:: Result < ( ) > {
80+ async fn run_dump_server_windows (
81+ output_path : PathBuf ,
82+ pipe_name : String ,
83+ mut first_server : tokio:: net:: windows:: named_pipe:: NamedPipeServer ,
84+ ) -> anyhow:: Result < ( ) > {
6885 use tokio:: net:: windows:: named_pipe:: ServerOptions ;
6986
87+ // Handle first connection
88+ first_server. connect ( ) . await ?;
89+ handle_connection_async ( first_server, output_path. clone ( ) ) . await ;
90+
91+ // Handle subsequent connections
7092 loop {
71- // Create server instance
93+ // Create server instance (not the first one)
7294 let server = ServerOptions :: new ( )
73- . first_pipe_instance ( true )
95+ . first_pipe_instance ( false )
7496 . create ( & pipe_name) ?;
7597
7698 // Wait for client connection
@@ -85,6 +107,8 @@ async fn run_dump_server_windows(output_path: PathBuf, pipe_name: String) -> any
85107/// Returns the named pipe path that the server is listening on
86108#[ cfg( windows) ]
87109pub ( crate ) fn spawn_dump_server ( output_path : PathBuf ) -> anyhow:: Result < PathBuf > {
110+ use tokio:: net:: windows:: named_pipe:: ServerOptions ;
111+
88112 // Create a unique named pipe name with randomness to avoid collisions
89113 let random_id: u64 = rand:: random ( ) ;
90114 let pipe_name = format ! (
@@ -95,17 +119,22 @@ pub(crate) fn spawn_dump_server(output_path: PathBuf) -> anyhow::Result<PathBuf>
95119 let pipe_path = PathBuf :: from ( & pipe_name) ;
96120
97121 let ( tx, rx) = std:: sync:: mpsc:: channel ( ) ;
98-
122+
99123 std:: thread:: spawn ( move || {
100124 // Top-level error handler - all errors logged here
101125 let result = ( || -> anyhow:: Result < ( ) > {
102126 let rt = tokio:: runtime:: Runtime :: new ( ) ?;
103127 rt. block_on ( async {
128+ // Create the first pipe instance before signaling ready
129+ let first_server = ServerOptions :: new ( )
130+ . first_pipe_instance ( true )
131+ . create ( & pipe_name) ?;
132+
104133 tx. send ( Ok ( ( ) ) ) ?;
105- run_dump_server_windows ( output_path, pipe_name) . await
134+ run_dump_server_windows ( output_path, pipe_name, first_server ) . await
106135 } )
107136 } ) ( ) ;
108-
137+
109138 if let Err ( e) = result {
110139 eprintln ! ( "[dump-server] Error: {}" , e) ;
111140 let _ = tx. send ( Err ( e) ) ;
0 commit comments