1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 //! Asynchronous `Connector` trait and `HttpConnector` implementation.
15
16 mod stream;
17
18 use core::future::Future;
19 use std::io::{Error, ErrorKind};
20 use std::sync::Arc;
21
22 /// Information of an IO.
23 pub use stream::ConnInfo;
24 use ylong_http::request::uri::Uri;
25
26 use crate::async_impl::dns::{DefaultDnsResolver, EyeBallConfig, HappyEyeballs, Resolver};
27 use crate::runtime::{AsyncRead, AsyncWrite, TcpStream};
28 use crate::util::config::ConnectorConfig;
29 use crate::{HttpClientError, Timeout};
30
31 /// `Connector` trait used by `async_impl::Client`. `Connector` provides
32 /// asynchronous connection establishment interfaces.
33 pub trait Connector {
34 /// Streams that this connector produces.
35 type Stream: AsyncRead + AsyncWrite + ConnInfo + Unpin + Sync + Send + 'static;
36
37 /// Futures generated by this connector when attempting to create a stream.
38 type Future: Future<Output = Result<Self::Stream, HttpClientError>>
39 + Unpin
40 + Sync
41 + Send
42 + 'static;
43
44 /// Attempts to establish a connection.
connect(&self, uri: &Uri) -> Self::Future45 fn connect(&self, uri: &Uri) -> Self::Future;
46 }
47
48 /// Connector for creating HTTP or HTTPS connections asynchronously.
49 ///
50 /// `HttpConnector` implements `async_impl::Connector` trait.
51 pub struct HttpConnector {
52 config: ConnectorConfig,
53 resolver: Arc<dyn Resolver>,
54 }
55
56 impl HttpConnector {
57 /// Creates a new `HttpConnector` with a `ConnectorConfig`.
new(config: ConnectorConfig, resolver: Arc<dyn Resolver>) -> Self58 pub(crate) fn new(config: ConnectorConfig, resolver: Arc<dyn Resolver>) -> Self {
59 Self { config, resolver }
60 }
61
62 /// Creates a new `HttpConnector` with a given dns `Resolver`.
with_dns_resolver<R>(resolver: R) -> Self where R: Resolver,63 pub(crate) fn with_dns_resolver<R>(resolver: R) -> Self
64 where
65 R: Resolver,
66 {
67 let resolver = Arc::new(resolver) as Arc<dyn Resolver>;
68 Self {
69 config: Default::default(),
70 resolver,
71 }
72 }
73 }
74
75 impl Default for HttpConnector {
default() -> Self76 fn default() -> Self {
77 Self {
78 config: Default::default(),
79 resolver: Arc::new(DefaultDnsResolver::default()),
80 }
81 }
82 }
83
tcp_stream(eyeballs: HappyEyeballs) -> Result<TcpStream, HttpClientError>84 async fn tcp_stream(eyeballs: HappyEyeballs) -> Result<TcpStream, HttpClientError> {
85 eyeballs
86 .connect()
87 .await
88 .map_err(|e| {
89 #[cfg(target_os = "linux")]
90 if format!("{}", e).contains("failed to lookup address information") {
91 return HttpClientError::from_dns_error(crate::ErrorKind::Connect, e);
92 }
93 #[cfg(target_os = "windows")]
94 if let Some(code) = e.raw_os_error() {
95 if (0x2329..=0x26B2).contains(&code) || code == 0x2AF9 {
96 return HttpClientError::from_dns_error(crate::ErrorKind::Connect, e);
97 }
98 }
99 HttpClientError::from_io_error(crate::ErrorKind::Connect, e)
100 })
101 .and_then(|stream| match stream.set_nodelay(true) {
102 Ok(()) => Ok(stream),
103 Err(e) => err_from_io!(Connect, e),
104 })
105 }
106
eyeballs_connect( resolver: Arc<dyn Resolver>, addr: &str, timeout: Timeout, ) -> Result<TcpStream, HttpClientError>107 async fn eyeballs_connect(
108 resolver: Arc<dyn Resolver>,
109 addr: &str,
110 timeout: Timeout,
111 ) -> Result<TcpStream, HttpClientError> {
112 let addr_fut = resolver.resolve(addr);
113 let socket_addr = addr_fut.await.map_err(|e| {
114 HttpClientError::from_dns_error(
115 crate::ErrorKind::Connect,
116 Error::new(ErrorKind::Interrupted, e),
117 )
118 })?;
119
120 let addrs = socket_addr.collect::<Vec<_>>();
121 let eyeball_config = EyeBallConfig::new(timeout.inner(), None);
122 let happy_eyeballs = HappyEyeballs::new(addrs, eyeball_config);
123 tcp_stream(happy_eyeballs).await
124 }
125
126 #[cfg(not(feature = "__tls"))]
127 mod no_tls {
128 use core::future::Future;
129 use core::pin::Pin;
130
131 use ylong_http::request::uri::Uri;
132
133 use super::{eyeballs_connect, Connector, HttpConnector};
134 use crate::async_impl::connector::stream::HttpStream;
135 use crate::async_impl::interceptor::{ConnDetail, ConnProtocol};
136 use crate::runtime::TcpStream;
137 use crate::HttpClientError;
138
139 impl Connector for HttpConnector {
140 type Stream = HttpStream<TcpStream>;
141 type Future =
142 Pin<Box<dyn Future<Output = Result<Self::Stream, HttpClientError>> + Sync + Send>>;
143
connect(&self, uri: &Uri) -> Self::Future144 fn connect(&self, uri: &Uri) -> Self::Future {
145 // Checks if this uri need be proxied.
146 let mut is_proxy = false;
147 let mut addr = uri.authority().unwrap().to_string();
148 if let Some(proxy) = self.config.proxies.match_proxy(uri) {
149 addr = proxy.via_proxy(uri).authority().unwrap().to_string();
150 is_proxy = true;
151 }
152 let resolver = self.resolver.clone();
153 let timeout = self.config.timeout.clone();
154 Box::pin(async move {
155 let stream = eyeballs_connect(resolver, addr.as_str(), timeout).await?;
156 let local = stream
157 .local_addr()
158 .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?;
159 let peer = stream
160 .peer_addr()
161 .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?;
162 let detail = ConnDetail {
163 protocol: ConnProtocol::Tcp,
164 alpn: None,
165 local,
166 peer,
167 addr,
168 proxy: is_proxy,
169 };
170 Ok(HttpStream::new(stream, detail))
171 })
172 }
173 }
174 }
175
176 #[cfg(feature = "__tls")]
177 mod tls {
178 use core::future::Future;
179 use core::pin::Pin;
180 use std::error;
181 use std::fmt::{Debug, Display, Formatter};
182 use std::io::{Error, ErrorKind, Write};
183
184 use ylong_http::request::uri::{Scheme, Uri};
185
186 use super::{eyeballs_connect, Connector, HttpConnector};
187 use crate::async_impl::connector::stream::HttpStream;
188 use crate::async_impl::interceptor::{ConnDetail, ConnProtocol};
189 use crate::async_impl::ssl_stream::{AsyncSslStream, MixStream};
190 #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__c_openssl"))]
191 use crate::config::FchownConfig;
192 use crate::runtime::{AsyncReadExt, AsyncWriteExt, TcpStream};
193 use crate::{HttpClientError, TlsConfig};
194
195 impl Connector for HttpConnector {
196 type Stream = HttpStream<MixStream<TcpStream>>;
197 type Future =
198 Pin<Box<dyn Future<Output = Result<Self::Stream, HttpClientError>> + Sync + Send>>;
199
connect(&self, uri: &Uri) -> Self::Future200 fn connect(&self, uri: &Uri) -> Self::Future {
201 // Make sure all parts of uri is accurate.
202 let mut addr = uri.authority().unwrap().to_string();
203 let mut auth = None;
204 let mut is_proxy = false;
205
206 if let Some(proxy) = self.config.proxies.match_proxy(uri) {
207 addr = proxy.via_proxy(uri).authority().unwrap().to_string();
208 auth = proxy
209 .intercept
210 .proxy_info()
211 .basic_auth
212 .as_ref()
213 .and_then(|v| v.to_string().ok());
214 is_proxy = true;
215 }
216 #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__c_openssl"))]
217 let fchown = self.config.fchown.clone();
218 let resolver = self.resolver.clone();
219 let timeout = self.config.timeout.clone();
220 match *uri.scheme().unwrap() {
221 Scheme::HTTP => Box::pin(async move {
222 let stream = eyeballs_connect(resolver, addr.as_str(), timeout).await?;
223
224 #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))]
225 if let Some(fchown) = fchown {
226 let _ = stream.fchown(fchown.uid, fchown.gid);
227 }
228
229 let local = stream.local_addr().map_err(|e| {
230 HttpClientError::from_io_error(crate::ErrorKind::Connect, e)
231 })?;
232 let peer = stream.peer_addr().map_err(|e| {
233 HttpClientError::from_io_error(crate::ErrorKind::Connect, e)
234 })?;
235 let detail = ConnDetail {
236 protocol: ConnProtocol::Tcp,
237 alpn: None,
238 local,
239 peer,
240 addr,
241 proxy: is_proxy,
242 };
243
244 Ok(HttpStream::new(MixStream::Http(stream), detail))
245 }),
246 Scheme::HTTPS => {
247 let host = uri.host().unwrap().to_string();
248 let port = uri.port().unwrap().as_u16().unwrap();
249 let config = self.config.tls.clone();
250 Box::pin(async move {
251 let stream = eyeballs_connect(resolver, addr.as_str(), timeout).await?;
252 #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))]
253 {
254 https_connect(
255 config,
256 addr,
257 stream,
258 is_proxy,
259 auth,
260 (host, port),
261 fchown,
262 )
263 .await
264 }
265 #[cfg(not(all(
266 target_os = "linux",
267 feature = "ylong_base",
268 feature = "__c_openssl"
269 )))]
270 {
271 https_connect(config, addr, stream, is_proxy, auth, (host, port)).await
272 }
273 })
274 }
275 }
276 }
277 }
278
https_connect( config: TlsConfig, addr: String, tcp_stream: TcpStream, is_proxy: bool, auth: Option<String>, (host, port): (String, u16), #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))] fchown: Option< FchownConfig, >, ) -> Result<HttpStream<MixStream<TcpStream>>, HttpClientError>279 async fn https_connect(
280 config: TlsConfig,
281 addr: String,
282 tcp_stream: TcpStream,
283 is_proxy: bool,
284 auth: Option<String>,
285 (host, port): (String, u16),
286 #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))] fchown: Option<
287 FchownConfig,
288 >,
289 ) -> Result<HttpStream<MixStream<TcpStream>>, HttpClientError> {
290 let mut tcp = tcp_stream;
291 #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))]
292 if let Some(fchown) = fchown {
293 let _ = tcp.fchown(fchown.uid, fchown.gid);
294 }
295 let local = tcp
296 .local_addr()
297 .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?;
298 let peer = tcp
299 .peer_addr()
300 .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?;
301 if is_proxy {
302 tcp = tunnel(tcp, &host, port, auth)
303 .await
304 .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?;
305 };
306
307 let pinned_key = config.pinning_host_match(addr.as_str());
308 let mut stream = config
309 .ssl_new(&host)
310 .and_then(|ssl| AsyncSslStream::new(ssl.into_inner(), tcp, pinned_key))
311 .map_err(|e| {
312 HttpClientError::from_tls_error(
313 crate::ErrorKind::Connect,
314 Error::new(ErrorKind::Other, e),
315 )
316 })?;
317
318 Pin::new(&mut stream).connect().await.map_err(|e| {
319 HttpClientError::from_tls_error(
320 crate::ErrorKind::Connect,
321 Error::new(ErrorKind::Other, e),
322 )
323 })?;
324
325 let alpn = stream.negotiated_alpn_protocol().map(Vec::from);
326 let detail = ConnDetail {
327 protocol: ConnProtocol::Tcp,
328 alpn,
329 local,
330 peer,
331 addr,
332 proxy: is_proxy,
333 };
334
335 Ok(HttpStream::new(MixStream::Https(stream), detail))
336 }
337
tunnel( mut conn: TcpStream, host: &str, port: u16, auth: Option<String>, ) -> Result<TcpStream, Error>338 async fn tunnel(
339 mut conn: TcpStream,
340 host: &str,
341 port: u16,
342 auth: Option<String>,
343 ) -> Result<TcpStream, Error> {
344 let mut req = Vec::new();
345
346 write!(
347 &mut req,
348 "CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n"
349 )?;
350
351 if let Some(value) = auth {
352 write!(&mut req, "Proxy-Authorization: Basic {value}\r\n")?;
353 }
354
355 write!(&mut req, "\r\n")?;
356
357 conn.write_all(&req).await?;
358
359 let mut buf = [0; 8192];
360 let mut pos = 0;
361
362 loop {
363 let n = conn.read(&mut buf[pos..]).await?;
364
365 if n == 0 {
366 return Err(other_io_error(CreateTunnelErr::Unsuccessful));
367 }
368
369 pos += n;
370 let resp = &buf[..pos];
371 if resp.starts_with(b"HTTP/1.1 200") || resp.starts_with(b"HTTP/1.0 200") {
372 if resp.ends_with(b"\r\n\r\n") {
373 return Ok(conn);
374 }
375 if pos == buf.len() {
376 return Err(other_io_error(CreateTunnelErr::ProxyHeadersTooLong));
377 }
378 } else if resp.starts_with(b"HTTP/1.1 407") {
379 return Err(other_io_error(CreateTunnelErr::ProxyAuthenticationRequired));
380 } else {
381 return Err(other_io_error(CreateTunnelErr::Unsuccessful));
382 }
383 }
384 }
385
other_io_error(err: CreateTunnelErr) -> Error386 fn other_io_error(err: CreateTunnelErr) -> Error {
387 Error::new(ErrorKind::Other, err)
388 }
389
390 enum CreateTunnelErr {
391 ProxyHeadersTooLong,
392 ProxyAuthenticationRequired,
393 Unsuccessful,
394 }
395
396 impl Debug for CreateTunnelErr {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result397 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
398 match self {
399 Self::ProxyHeadersTooLong => f.write_str("Proxy headers too long for tunnel"),
400 Self::ProxyAuthenticationRequired => f.write_str("Proxy authentication required"),
401 Self::Unsuccessful => f.write_str("Unsuccessful tunnel"),
402 }
403 }
404 }
405
406 impl Display for CreateTunnelErr {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result407 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
408 Debug::fmt(self, f)
409 }
410 }
411
412 impl error::Error for CreateTunnelErr {}
413
414 #[cfg(all(test, feature = "__tls"))]
415 mod ut_tunnel_error_debug {
416 use crate::async_impl::connector::tls::CreateTunnelErr;
417
418 /// UT test cases for debug of`CreateTunnelErr`.
419 ///
420 /// # Brief
421 /// 1. Checks `CreateTunnelErr` debug by calling `CreateTunnelErr::fmt`.
422 /// 2. Checks if the result is as expected.
423 #[test]
ut_tunnel_error_debug_assert()424 fn ut_tunnel_error_debug_assert() {
425 assert_eq!(
426 format!("{:?}", CreateTunnelErr::ProxyHeadersTooLong),
427 "Proxy headers too long for tunnel"
428 );
429 assert_eq!(
430 format!("{:?}", CreateTunnelErr::ProxyAuthenticationRequired),
431 "Proxy authentication required"
432 );
433 assert_eq!(
434 format!("{:?}", CreateTunnelErr::Unsuccessful),
435 "Unsuccessful tunnel"
436 );
437 assert_eq!(
438 format!("{}", CreateTunnelErr::ProxyHeadersTooLong),
439 "Proxy headers too long for tunnel"
440 );
441 assert_eq!(
442 format!("{}", CreateTunnelErr::ProxyAuthenticationRequired),
443 "Proxy authentication required"
444 );
445 assert_eq!(
446 format!("{}", CreateTunnelErr::Unsuccessful),
447 "Unsuccessful tunnel"
448 );
449 }
450 }
451
452 #[cfg(all(test, feature = "__tls", feature = "ylong_base"))]
453 mod ut_create_tunnel_err_debug {
454 use std::net::SocketAddr;
455 use std::str::FromStr;
456
457 use ylong_runtime::io::AsyncWriteExt;
458
459 use crate::async_impl::connector::tcp_stream;
460 use crate::async_impl::connector::tls::{other_io_error, tunnel, CreateTunnelErr};
461 use crate::async_impl::dns::{EyeBallConfig, HappyEyeballs};
462 use crate::start_tcp_server;
463 use crate::util::test_utils::{format_header_str, TcpHandle};
464
465 /// UT test cases for `tunnel`.
466 ///
467 /// # Brief
468 /// 1. Creates a `tcp stream` by calling `tcp_stream`.
469 /// 2. Sends a `Request` by `tunnel`.
470 /// 3. Checks if the result is as expected.
471 #[test]
ut_ssl_tunnel_error()472 fn ut_ssl_tunnel_error() {
473 let mut handles = vec![];
474 start_tcp_server!(
475 Handles: handles,
476 EndWith: "\r\n\r\n",
477 Shutdown: std::net::Shutdown::Both,
478 );
479 let handle = handles.pop().expect("No more handles !");
480
481 let eyeballs = HappyEyeballs::new(
482 vec![SocketAddr::from_str(handle.addr.as_str()).unwrap()],
483 EyeBallConfig::new(None, None),
484 );
485
486 let handle = ylong_runtime::spawn(async move {
487 let tcp = tcp_stream(eyeballs).await.unwrap();
488 let res = tunnel(
489 tcp,
490 "ylong_http.com",
491 443,
492 Some(String::from("base64 bytes")),
493 )
494 .await;
495 assert_eq!(
496 format!("{:?}", res.err()),
497 format!("{:?}", Some(other_io_error(CreateTunnelErr::Unsuccessful)))
498 );
499 handle
500 .server_shutdown
501 .recv()
502 .expect("server send order failed !");
503 });
504 ylong_runtime::block_on(handle).unwrap();
505
506 start_tcp_server!(
507 Handles: handles,
508 EndWith: "\r\n\r\n",
509 Response: {
510 Status: 407,
511 Version: "HTTP/1.1",
512 Header: "Content-Length", "11",
513 Body: "METHOD GET!",
514 },
515 Shutdown: std::net::Shutdown::Both,
516 );
517 let handle = handles.pop().expect("No more handles !");
518
519 let eyeballs = HappyEyeballs::new(
520 vec![SocketAddr::from_str(handle.addr.as_str()).unwrap()],
521 EyeBallConfig::new(None, None),
522 );
523 let handle = ylong_runtime::spawn(async move {
524 let tcp = tcp_stream(eyeballs).await.unwrap();
525 let res = tunnel(
526 tcp,
527 "ylong_http.com",
528 443,
529 Some(String::from("base64 bytes")),
530 )
531 .await;
532 assert_eq!(
533 format!("{:?}", res.err()),
534 format!(
535 "{:?}",
536 Some(other_io_error(CreateTunnelErr::ProxyAuthenticationRequired))
537 )
538 );
539 handle
540 .server_shutdown
541 .recv()
542 .expect("server send order failed !");
543 });
544 ylong_runtime::block_on(handle).unwrap();
545
546 start_tcp_server!(
547 Handles: handles,
548 EndWith: "\r\n\r\n",
549 Response: {
550 Status: 402,
551 Version: "HTTP/1.1",
552 Header: "Content-Length", "11",
553 Body: "METHOD GET!",
554 },
555 Shutdown: std::net::Shutdown::Both,
556 );
557 let handle = handles.pop().expect("No more handles !");
558
559 let eyeballs = HappyEyeballs::new(
560 vec![SocketAddr::from_str(handle.addr.as_str()).unwrap()],
561 EyeBallConfig::new(None, None),
562 );
563 let handle = ylong_runtime::spawn(async move {
564 let tcp = tcp_stream(eyeballs).await.unwrap();
565 let res = tunnel(
566 tcp,
567 "ylong_http.com",
568 443,
569 Some(String::from("base64 bytes")),
570 )
571 .await;
572 assert_eq!(
573 format!("{:?}", res.err()),
574 format!("{:?}", Some(other_io_error(CreateTunnelErr::Unsuccessful)))
575 );
576 handle
577 .server_shutdown
578 .recv()
579 .expect("server send order failed !");
580 });
581 ylong_runtime::block_on(handle).unwrap();
582 }
583
584 /// UT test cases for `tunnel`.
585 ///
586 /// # Brief
587 /// 1. Creates a `tcp stream` by calling `tcp_stream`.
588 /// 2. Sends a `Request` by `tunnel`.
589 /// 3. Checks if the result is as expected.
590 #[test]
ut_ssl_tunnel_connect()591 fn ut_ssl_tunnel_connect() {
592 let mut handles = vec![];
593
594 start_tcp_server!(
595 Handles: handles,
596 EndWith: "\r\n\r\n",
597 Response: {
598 Status: 200,
599 Version: "HTTP/1.1",
600 Body: "",
601 },
602 Shutdown: std::net::Shutdown::Both,
603 );
604 let handle = handles.pop().expect("No more handles !");
605
606 let eyeballs = HappyEyeballs::new(
607 vec![SocketAddr::from_str(handle.addr.as_str()).unwrap()],
608 EyeBallConfig::new(None, None),
609 );
610 let handle = ylong_runtime::spawn(async move {
611 let tcp = tcp_stream(eyeballs).await.unwrap();
612 let res = tunnel(
613 tcp,
614 "ylong_http.com",
615 443,
616 Some(String::from("base64 bytes")),
617 )
618 .await;
619 assert!(res.is_ok());
620 handle
621 .server_shutdown
622 .recv()
623 .expect("server send order failed !");
624 });
625 ylong_runtime::block_on(handle).unwrap();
626 }
627
628 /// UT test cases for response beyond size of `tunnel`.
629 ///
630 /// # Brief
631 /// 1. Creates a `tcp stream` by calling `tcp_stream`.
632 /// 2. Sends a `Request` by `tunnel`.
633 /// 3. Checks if the result is as expected.
634 #[test]
ut_ssl_tunnel_resp_beyond_size()635 fn ut_ssl_tunnel_resp_beyond_size() {
636 let mut handles = vec![];
637
638 let buf = vec![b'b'; 8192];
639 let body = String::from_utf8(buf).unwrap();
640
641 start_tcp_server!(
642 Handles: handles,
643 EndWith: "\r\n\r\n",
644 Response: {
645 Status: 200,
646 Version: "HTTP/1.1",
647 Header: "Content-Length", "11",
648 Body: body.as_str(),
649 },
650 );
651 let handle = handles.pop().expect("No more handles !");
652
653 let eyeballs = HappyEyeballs::new(
654 vec![SocketAddr::from_str(handle.addr.as_str()).unwrap()],
655 EyeBallConfig::new(None, None),
656 );
657 let handle = ylong_runtime::spawn(async move {
658 let tcp = tcp_stream(eyeballs).await.unwrap();
659 let res = tunnel(
660 tcp,
661 "ylong_http.com",
662 443,
663 Some(String::from("base64 bytes")),
664 )
665 .await;
666 assert_eq!(
667 format!("{:?}", res.err()),
668 format!(
669 "{:?}",
670 Some(other_io_error(CreateTunnelErr::ProxyHeadersTooLong))
671 )
672 );
673 handle
674 .server_shutdown
675 .recv()
676 .expect("server send order failed !");
677 });
678 ylong_runtime::block_on(handle).unwrap();
679 }
680 }
681 }
682