11/*
22 * SPDX-License-Identifier: Apache-2.0
33 * SPDX-FileCopyrightText: 2023-2025 ByteDance and/or its affiliates.
4+ * SPDX-FileCopyrightText: 2026 VEY-OSS Developers.
45 */
56
6- use std:: io;
7+ use std:: io:: { self , Write } ;
78
8- use tokio:: io:: { AsyncWrite , AsyncWriteExt , BufWriter } ;
9+ use tokio:: io:: { AsyncWrite , AsyncWriteExt } ;
910
1011use vey_types:: net:: UpstreamAddr ;
1112
1213/// the extra header lines should end with \r\n
1314pub struct HttpConnectRequest < ' a > {
14- host : & ' a UpstreamAddr ,
1515 static_headers : & ' a [ String ] ,
1616 dyn_headers : Vec < String > ,
1717}
1818
1919impl < ' a > HttpConnectRequest < ' a > {
20- pub fn new ( host : & ' a UpstreamAddr , static_headers : & ' a [ String ] ) -> Self {
20+ pub fn new ( static_headers : & ' a [ String ] ) -> Self {
2121 HttpConnectRequest {
22- host,
2322 static_headers,
2423 dyn_headers : Vec :: new ( ) ,
2524 }
@@ -30,28 +29,23 @@ impl<'a> HttpConnectRequest<'a> {
3029 self . dyn_headers . push ( line) ;
3130 }
3231
33- /// the extra header lines should end with \r\n
34- pub async fn send < W > ( & ' a self , writer : & mut W ) -> io:: Result < ( ) >
32+ pub async fn send < W > ( & ' a self , target : & UpstreamAddr , writer : & mut W ) -> io:: Result < ( ) >
3533 where
3634 W : AsyncWrite + Unpin ,
3735 {
38- let mut buf_writer = BufWriter :: new ( writer) ;
39- buf_writer
40- . write_all ( format ! ( "CONNECT {} HTTP/1.1\r \n " , self . host) . as_bytes ( ) )
41- . await ?;
42- buf_writer
43- . write_all ( format ! ( "Host: {}\r \n " , self . host) . as_bytes ( ) )
44- . await ?;
45- buf_writer. write_all ( b"Connection: keep-alive\r \n " ) . await ?;
36+ let mut buf = Vec :: with_capacity ( 256 ) ;
37+ write ! ( & mut buf, "CONNECT {target} HTTP/1.1\r \n " ) ?;
38+ buf. extend_from_slice ( b"Connection: keep-alive\r \n " ) ;
4639 for line in self . static_headers {
4740 debug_assert ! ( line. ends_with( "\r \n " ) ) ;
48- buf_writer . write_all ( line. as_bytes ( ) ) . await ? ;
41+ buf . extend_from_slice ( line. as_bytes ( ) ) ;
4942 }
5043 for line in & self . dyn_headers {
51- buf_writer . write_all ( line. as_bytes ( ) ) . await ? ;
44+ buf . extend_from_slice ( line. as_bytes ( ) ) ;
5245 }
53- buf_writer. write_all ( b"\r \n " ) . await ?;
54- buf_writer. flush ( ) . await
46+ buf. extend_from_slice ( b"\r \n " ) ;
47+ writer. write_all ( & buf) . await ?;
48+ writer. flush ( ) . await
5549 }
5650}
5751
@@ -60,166 +54,104 @@ mod tests {
6054 use super :: * ;
6155 use std:: str:: FromStr ;
6256
63- // Helper function to create a test UpstreamAddr
64- fn create_test_upstream_addr ( ) -> UpstreamAddr {
57+ fn test_upstream_addr ( ) -> UpstreamAddr {
6558 UpstreamAddr :: from_str ( "example.com:8080" ) . unwrap ( )
6659 }
6760
61+ async fn render_request ( request : & HttpConnectRequest < ' _ > , target : & UpstreamAddr ) -> String {
62+ let mut buffer = Vec :: new ( ) ;
63+ request. send ( target, & mut buffer) . await . unwrap ( ) ;
64+ String :: from_utf8 ( buffer) . unwrap ( )
65+ }
66+
6867 #[ test]
69- fn new ( ) {
70- let host = create_test_upstream_addr ( ) ;
68+ fn new_starts_with_static_headers_only ( ) {
7169 let static_headers = vec ! [
7270 "User-Agent: test-agent\r \n " . to_string( ) ,
7371 "Accept: */*\r \n " . to_string( ) ,
7472 ] ;
7573
76- let request = HttpConnectRequest :: new ( & host , & static_headers) ;
74+ let request = HttpConnectRequest :: new ( & static_headers) ;
7775
78- assert_eq ! ( request. host. host_str( ) , "example.com" ) ;
79- assert_eq ! ( request. host. port( ) , 8080 ) ;
8076 assert_eq ! ( request. static_headers. len( ) , 2 ) ;
8177 assert ! ( request. dyn_headers. is_empty( ) ) ;
8278 }
8379
8480 #[ test]
85- fn append_dyn_header ( ) {
86- let host = create_test_upstream_addr ( ) ;
81+ fn append_dyn_header_preserves_order ( ) {
8782 let static_headers: Vec < String > = Vec :: new ( ) ;
88- let mut request = HttpConnectRequest :: new ( & host , & static_headers) ;
83+ let mut request = HttpConnectRequest :: new ( & static_headers) ;
8984
90- // Appending a single dynamic header
9185 request. append_dyn_header ( "X-Custom-Header: value1\r \n " . to_string ( ) ) ;
92- assert_eq ! ( request. dyn_headers. len( ) , 1 ) ;
93- assert_eq ! ( request. dyn_headers[ 0 ] , "X-Custom-Header: value1\r \n " ) ;
94-
95- // Appending multiple dynamic headers
9686 request. append_dyn_header ( "X-Another-Header: value2\r \n " . to_string ( ) ) ;
87+
9788 assert_eq ! ( request. dyn_headers. len( ) , 2 ) ;
89+ assert_eq ! ( request. dyn_headers[ 0 ] , "X-Custom-Header: value1\r \n " ) ;
9890 assert_eq ! ( request. dyn_headers[ 1 ] , "X-Another-Header: value2\r \n " ) ;
9991 }
10092
10193 #[ tokio:: test]
102- async fn send_no_headers ( ) {
103- let host = create_test_upstream_addr ( ) ;
94+ async fn send_without_extra_headers ( ) {
95+ let target = test_upstream_addr ( ) ;
10496 let static_headers: Vec < String > = Vec :: new ( ) ;
105- let request = HttpConnectRequest :: new ( & host, & static_headers) ;
106-
107- let mut buffer = Vec :: new ( ) ;
108- request. send ( & mut buffer) . await . unwrap ( ) ;
109-
110- let output = String :: from_utf8 ( buffer) . unwrap ( ) ;
111- let expected = "CONNECT example.com:8080 HTTP/1.1\r \n \
112- Host: example.com:8080\r \n \
113- Connection: keep-alive\r \n \
114- \r \n ";
115-
116- assert_eq ! ( output, expected) ;
97+ let request = HttpConnectRequest :: new ( & static_headers) ;
98+
99+ assert_eq ! (
100+ render_request( & request, & target) . await ,
101+ "CONNECT example.com:8080 HTTP/1.1\r \n \
102+ Connection: keep-alive\r \n \
103+ \r \n "
104+ ) ;
117105 }
118106
119107 #[ tokio:: test]
120- async fn send_with_static_headers ( ) {
121- let host = create_test_upstream_addr ( ) ;
108+ async fn send_with_static_and_dynamic_headers ( ) {
109+ let target = test_upstream_addr ( ) ;
122110 let static_headers = vec ! [
123111 "User-Agent: test-agent\r \n " . to_string( ) ,
124112 "Accept: */*\r \n " . to_string( ) ,
125113 ] ;
126-
127- let request = HttpConnectRequest :: new ( & host, & static_headers) ;
128-
129- let mut buffer = Vec :: new ( ) ;
130- request. send ( & mut buffer) . await . unwrap ( ) ;
131-
132- let output = String :: from_utf8 ( buffer) . unwrap ( ) ;
133- let expected = "CONNECT example.com:8080 HTTP/1.1\r \n \
134- Host: example.com:8080\r \n \
135- Connection: keep-alive\r \n \
136- User-Agent: test-agent\r \n \
137- Accept: */*\r \n \
138- \r \n ";
139-
140- assert_eq ! ( output, expected) ;
141- }
142-
143- #[ tokio:: test]
144- async fn send_with_dynamic_headers ( ) {
145- let host = create_test_upstream_addr ( ) ;
146- let static_headers: Vec < String > = Vec :: new ( ) ;
147- let mut request = HttpConnectRequest :: new ( & host, & static_headers) ;
114+ let mut request = HttpConnectRequest :: new ( & static_headers) ;
148115
149116 request. append_dyn_header ( "X-Custom-Header: value1\r \n " . to_string ( ) ) ;
150117 request. append_dyn_header ( "X-Another-Header: value2\r \n " . to_string ( ) ) ;
151118
152- let mut buffer = Vec :: new ( ) ;
153- request. send ( & mut buffer) . await . unwrap ( ) ;
154-
155- let output = String :: from_utf8 ( buffer) . unwrap ( ) ;
156- let expected = "CONNECT example.com:8080 HTTP/1.1\r \n \
157- Host: example.com:8080\r \n \
158- Connection: keep-alive\r \n \
159- X-Custom-Header: value1\r \n \
160- X-Another-Header: value2\r \n \
161- \r \n ";
162-
163- assert_eq ! ( output, expected) ;
164- }
165-
166- #[ tokio:: test]
167- async fn send_with_both_header_types ( ) {
168- let host = create_test_upstream_addr ( ) ;
169- let static_headers = vec ! [
170- "User-Agent: test-agent\r \n " . to_string( ) ,
171- "Accept: */*\r \n " . to_string( ) ,
172- ] ;
173-
174- let mut request = HttpConnectRequest :: new ( & host, & static_headers) ;
175- request. append_dyn_header ( "X-Custom-Header: value1\r \n " . to_string ( ) ) ;
176- request. append_dyn_header ( "X-Another-Header: value2\r \n " . to_string ( ) ) ;
177-
178- let mut buffer = Vec :: new ( ) ;
179- request. send ( & mut buffer) . await . unwrap ( ) ;
180-
181- let output = String :: from_utf8 ( buffer) . unwrap ( ) ;
182- let expected = "CONNECT example.com:8080 HTTP/1.1\r \n \
183- Host: example.com:8080\r \n \
184- Connection: keep-alive\r \n \
185- User-Agent: test-agent\r \n \
186- Accept: */*\r \n \
187- X-Custom-Header: value1\r \n \
188- X-Another-Header: value2\r \n \
189- \r \n ";
190-
191- assert_eq ! ( output, expected) ;
119+ assert_eq ! (
120+ render_request( & request, & target) . await ,
121+ "CONNECT example.com:8080 HTTP/1.1\r \n \
122+ Connection: keep-alive\r \n \
123+ User-Agent: test-agent\r \n \
124+ Accept: */*\r \n \
125+ X-Custom-Header: value1\r \n \
126+ X-Another-Header: value2\r \n \
127+ \r \n "
128+ ) ;
192129 }
193130
194131 #[ tokio:: test]
195132 async fn send_with_ipv6_host ( ) {
196- let host = UpstreamAddr :: from_str ( "[2001:db8::1]:8080" ) . unwrap ( ) ;
133+ let target = UpstreamAddr :: from_str ( "[2001:db8::1]:8080" ) . unwrap ( ) ;
197134 let static_headers: Vec < String > = Vec :: new ( ) ;
198- let request = HttpConnectRequest :: new ( & host, & static_headers) ;
199-
200- let mut buffer = Vec :: new ( ) ;
201- request. send ( & mut buffer) . await . unwrap ( ) ;
202-
203- let output = String :: from_utf8 ( buffer) . unwrap ( ) ;
204- let expected = "CONNECT [2001:db8::1]:8080 HTTP/1.1\r \n \
205- Host: [2001:db8::1]:8080\r \n \
206- Connection: keep-alive\r \n \
207- \r \n ";
208-
209- assert_eq ! ( output, expected) ;
135+ let request = HttpConnectRequest :: new ( & static_headers) ;
136+
137+ assert_eq ! (
138+ render_request( & request, & target) . await ,
139+ "CONNECT [2001:db8::1]:8080 HTTP/1.1\r \n \
140+ Connection: keep-alive\r \n \
141+ \r \n "
142+ ) ;
210143 }
211144
212145 #[ tokio:: test]
213- async fn send_flush_behavior ( ) {
214- let host = create_test_upstream_addr ( ) ;
146+ async fn send_propagates_writer_error ( ) {
147+ let target = test_upstream_addr ( ) ;
215148 let static_headers: Vec < String > = Vec :: new ( ) ;
216- let request = HttpConnectRequest :: new ( & host , & static_headers) ;
217-
218- let mut buffer = Vec :: new ( ) ;
219- let result = request . send ( & mut buffer ) . await ;
149+ let request = HttpConnectRequest :: new ( & static_headers) ;
150+ let mut writer = tokio_test :: io :: Builder :: new ( )
151+ . write_error ( io :: Error :: new ( io :: ErrorKind :: BrokenPipe , "write failed" ) )
152+ . build ( ) ;
220153
221- assert ! ( result. is_ok( ) ) ;
222- // Verify that data was actually written to the buffer
223- assert ! ( !buffer. is_empty( ) ) ;
154+ let err = request. send ( & target, & mut writer) . await . unwrap_err ( ) ;
155+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: BrokenPipe ) ;
224156 }
225157}
0 commit comments