惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
K
Kaspersky official blog
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
AWS News Blog
AWS News Blog
V2EX - 技术
V2EX - 技术
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
Attack and Defense Labs
Attack and Defense Labs
Jina AI
Jina AI
The Last Watchdog
The Last Watchdog
W
WeLiveSecurity
H
Help Net Security
V
Visual Studio Blog
宝玉的分享
宝玉的分享
C
Cybersecurity and Infrastructure Security Agency CISA
T
Threat Research - Cisco Blogs
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
Latest news
Latest news
T
Tor Project blog
I
Intezer
美团技术团队
GbyAI
GbyAI
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 三生石上(FineUI控件)
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
Y
Y Combinator Blog
博客园 - 司徒正美
T
Tenable Blog
O
OpenAI News
N
News and Events Feed by Topic
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
云风的 BLOG
云风的 BLOG
Help Net Security
Help Net Security

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工具? | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog 对博客与笔记的思考 | Yesterday17's Blog Project Anni 之旅(3)自动化 Flutter 应用 CI/CD 上架流程 | Yesterday17's Blog AsobiStage 直接播放链接 | Yesterday17 如何在后分P时代进行投稿——sswa使用详解 | Yesterday17's Blog JSON RPC 与 LSP 协议基础 | Yesterday17's Blog Grajapa Shueisha / BookEnd 加密方式调查 | Yesterday17's Blog 【2022篇+WriteUp】如何再收一个新年红包? | Yesterday17's Blog 如何将良心云的良心功能清理干净 | Yesterday17's Blog 【油猴脚本】bilibili 投稿页面返回旧版+旧版页面强制允许分P上传 | Yesterday17's Blog Cloudr1v1 授权方式分析 | Yesterday17 Typora 1.0.2 逆向实录 | Yesterday17's Blog Project Anni 之旅(2)ValueAfterTable——toml-rs的实现与限制 | Yesterday17's Blog IPv4透明代理+IPv6 Passthrough——树莓派单臂软路由折腾记 | Yesterday17's Blog Chaos; Child 汉化补丁 神秘编码探索 | Yesterday17's Blog 镣铐与舞蹈——个性与共性之迷思 | Yesterday17's Blog Go 学习笔记 02 - 找准 io 之道 | Yesterday17's Blog NAT Slipstreaming v1 原理浅析 | Yesterday17's Blog 绕过「9-nine-」的 CDKEY 验证——KrkrPlugin 正(?)向实录 | Yesterday17's Blog 静流的青春纪念册——「サクラノ刻 -櫻の森の下を歩む-」体验版感言 | Yesterday17's Blog Project Anni 之旅 01 - 从 clap-builder 到 derive | Yesterday17's Blog [Google CTF 2021] CPP WriteUp | Yesterday17's Blog 获取 アソビステージ 的实际播放链接 | Yesterday17's Blog 90 行 Rust 代码实现 AsyncTeeReader | Yesterday17's Blog 或许还算有价值一读的文章列表 | Yesterday17's Blog 从零开始的 Seedbox 之旅 | Yesterday17's Blog [随笔]技术型博客行文迷思(1) | Yesterday17's Blog 浅谈 git fetch 的工作方式 | Yesterday17's Blog 『ソーサレス*アライヴ! ~the World's End Fallen Star~』通关感想" | Yesterday17's Blog Rust std::fmt 格式语法简述 | Yesterday17's Blog 日亚修改居住国的解决方案 | Yesterday17's Blog [Windows/Linux] GC553 的 Switch 完美采集之路 | Yesterday17's Blog 【翻译】Subtyping and Variance / 子类型与变型 | Yesterday17's Blog Berd's Red Envelope 2021 WriteUp | Yesterday17's Blog 【中英对照】ALSA 音频 API 使用教程/A Tutorial on Using the ALSA Audio API | Yesterday17's Blog 从 cue_scanner.l 看 CUE Sheet 的词法单元 | Yesterday17's Blog Postman 历史记录导出的解决方案 | Yesterday17's Blog 《恋爱绮谭 不存在的夏天》通关感想 | Yesterday17's Blog [微机实验/TD-PITE] 微机接口综合实验 | Yesterday17's Blog [微机实验/TD-PITE] 键盘扫描及数码管显示实验 | Yesterday17's Blog [微机实验/TD-PITE] 数码管显示实验 | Yesterday17's Blog Airsonic Advanced+Google Drive+Caddy 部署纪实 | Yesterday17's Blog X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog [微机实验/TD-PITE] 8251 串行接口实验 | Yesterday17's Blog Node.js child_process.fork 与 env 污染 RCE | Yesterday17's Blog EP.01 「夜の向日葵」 | Yesterday17's Blog [微机实验/TD-PITE] 8254 定时/计数器实验+选做实验 | Yesterday17's Blog [JLU CTF/2020] babywasm WriteUp | Yesterday17's Blog PHP 反序列化与经典利用 | Yesterday17's Blog WebAssembly 逆向简述 | Yesterday17's Blog 『彼女、お借りします』一期完结点评 | Yesterday17's Blog [微机实验/TD-PITE] D/A 转换实验+选做实验 | Yesterday17's Blog [微机实验/TD-PITE] A/D 转换实验+选做实验 | Yesterday17's Blog 开源项目申请 JetBrains Open Source License 简单流程 | Yesterday17's Blog 微软拼音与 JetBrains 搜索快捷键冲突的解决方案 | Yesterday17's Blog [微机实验/TD-PITE] 8259 中断优先级实验+选做实验 | Yesterday17's Blog IFTTT 测试(续) | Yesterday17 IFTTT 测试 | Yesterday17's Blog [微机实验/TD-PITE] 存储器扩展实验+选做实验 | Yesterday17's Blog 新版 GCC 针对 -fdump-translation-unit 的替代方案 | Yesterday17's Blog 一次 HSTS 策略配置的排错之旅 | Yesterday17's Blog YukiNative 踩坑记——Windows 的消息队列 | Yesterday17's Blog 我是我自己——论获取 HTTPS 证书时的验证步骤 | Yesterday17's Blog 【设计文档】对 PUG 的大规模设计修订(1.1) | Yesterday17's Blog GS65 折腾记(2)加装固态,分区,Grub2 引导 Manjaro LiveCD | Yesterday17's Blog 「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | Yesterday17's Blog GSuite 探索篇(1)使用 Service Account 向 Google Drive 传输文件 | Yesterday17's Blog 『サクラノ詩 -櫻の森の上を舞う-』通关感想 | Yesterday17's Blog 《ATRI -My Dear Moments-》通关感想 | Yesterday17's Blog [工具][VSCode 扩展] AegiKit——方便 Aegisub 使用的工具箱 | Yesterday17's Blog 贝塞尔曲线、字体矢量化与曲线运算 | Yesterday17's Blog NAT 类型初探 | Yesterday17's Blog
Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog
Yesterday17 · 2024-03-30 · via Yesterday17's Blog

在讲完一些有的没的(?)之后,我们继续回到 Pingora 的主线。这一篇我们来看一看 Peer,也就是 pingora-core::upstreams 中的一些结构与实现。

ToC

  • 从 Example 继续
  • trait Peer
    • Tracing 与 Tracer
  • BasicPeer
  • HttpPeer
    • Proxy

从 Example 继续

回到 Getting Started 开头的 Example。这次我们的注意力放在 HttpPeer 上:

use async_trait::async_trait;

use pingora::prelude::*;

use std::sync::Arc;

pub struct LB(Arc<LoadBalancer<RoundRobin>>);

#[async_trait]

impl ProxyHttp for LB {

7 collapsed lines

/// For this small example, we don't need context storage

type CTX = ();

fn new_ctx(&self) -> () {

()

}

async fn upstream_peer(&self, _session: &mut Session, _ctx: &mut ()) -> Result<Box<HttpPeer>> {

let upstream = self

.0

.select(b"", 256) // hash doesn't matter for round robin

.unwrap();

println!("upstream peer is: {upstream:?}");

// Set SNI to one.one.one.one

let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));

Ok(peer)

}

25 collapsed lines

async fn upstream_request_filter(

&self,

_session: &mut Session,

upstream_request: &mut RequestHeader,

_ctx: &mut Self::CTX,

) -> Result<()> {

upstream_request

.insert_header("Host", "one.one.one.one")

.unwrap();

Ok(())

}

}

fn main() {

let mut my_server = Server::new(None).unwrap();

my_server.bootstrap();

let upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();

let mut lb = http_proxy_service(&my_server.configuration, LB(Arc::new(upstreams)));

lb.add_tcp("0.0.0.0:6188");

my_server.add_service(lb);

my_server.run_forever();

}

可以看到,在实现 ProxyHttp 的过程中,我们需要返回一个 HttpPeer 用于连接。HttpPeer,以及它实现了的 trait Peer,都是定义在 pingora-core 中的结构。让我们深入看看。

trait Peer

PeerConnector 在连接时用于承载连接参数的结构。它的 Trait 定义如下:

/// [`Peer`] defines the interface to communicate with the [`crate::connectors`] regarding where to

/// connect to and how to connect to it.

pub trait Peer: Display + Clone {

/// The remote address to connect to

fn address(&self) -> &SocketAddr;

/// If TLS should be used;

fn tls(&self) -> bool;

/// The SNI to send, if TLS is used

fn sni(&self) -> &str;

/// To decide whether a [`Peer`] can use the connection established by another [`Peer`].

///

/// The connection to two peers are considered reusable to each other if their reuse hashes are

/// the same

fn reuse_hash(&self) -> u64;

103 collapsed lines

/// Get the proxy setting to connect to the remote server

fn get_proxy(&self) -> Option<&Proxy> {

None

}

/// Get the additional options to connect to the peer.

///

/// See [`PeerOptions`] for more details

fn get_peer_options(&self) -> Option<&PeerOptions> {

None

}

/// Get the additional options for modification.

fn get_mut_peer_options(&mut self) -> Option<&mut PeerOptions> {

None

}

/// Whether the TLS handshake should validate the cert of the server.

fn verify_cert(&self) -> bool {

match self.get_peer_options() {

Some(opt) => opt.verify_cert,

None => false,

}

}

/// Whether the TLS handshake should verify that the server cert matches the SNI.

fn verify_hostname(&self) -> bool {

match self.get_peer_options() {

Some(opt) => opt.verify_hostname,

None => false,

}

}

/// The alternative common name to use to verify the server cert.

///

/// If the server cert doesn't match the SNI, this name will be used to

/// verify the cert.

fn alternative_cn(&self) -> Option<&String> {

match self.get_peer_options() {

Some(opt) => opt.alternative_cn.as_ref(),

None => None,

}

}

/// Which local source address this connection should be bind to.

fn bind_to(&self) -> Option<&InetSocketAddr> {

match self.get_peer_options() {

Some(opt) => opt.bind_to.as_ref(),

None => None,

}

}

/// How long connect() call should be wait before it returns a timeout error.

fn connection_timeout(&self) -> Option<Duration> {

match self.get_peer_options() {

Some(opt) => opt.connection_timeout,

None => None,

}

}

/// How long the overall connection establishment should take before a timeout error is returned.

fn total_connection_timeout(&self) -> Option<Duration> {

match self.get_peer_options() {

Some(opt) => opt.total_connection_timeout,

None => None,

}

}

/// If the connection can be reused, how long the connection should wait to be reused before it

/// shuts down.

fn idle_timeout(&self) -> Option<Duration> {

self.get_peer_options().and_then(|o| o.idle_timeout)

}

/// Get the ALPN preference.

fn get_alpn(&self) -> Option<&ALPN> {

self.get_peer_options().map(|opt| &opt.alpn)

}

/// Get the CA cert to use to validate the server cert.

///

/// If not set, the default CAs will be used.

fn get_ca(&self) -> Option<&Arc<Box<[X509]>>> {

match self.get_peer_options() {

Some(opt) => opt.ca.as_ref(),

None => None,

}

}

/// Get the client cert and key for mutual TLS if any

fn get_client_cert_key(&self) -> Option<&Arc<CertKey>> {

None

}

/// The TCP keepalive setting that should be applied to this connection

fn tcp_keepalive(&self) -> Option<&TcpKeepalive> {

self.get_peer_options()

.and_then(|o| o.tcp_keepalive.as_ref())

}

/// The interval H2 pings to send to the server if any

fn h2_ping_interval(&self) -> Option<Duration> {

self.get_peer_options().and_then(|o| o.h2_ping_interval)

}

fn matches_fd<V: AsRawFd>(&self, fd: V) -> bool {

self.address().check_fd_match(fd)

}

fn get_tracer(&self) -> Option<Tracer> {

None

}

}

为了节省篇幅,这里将大量拥有默认实现的 trait Method 折叠了。有需要可以自行展开观看。从最核心的角度出发,Peer 必须实现的,也就是连接的过程中必须告知 Connector 的属性有:

方法简介
address()连接的地址。
tls()是否要以 TLS 连接。
sni()当使用 TLS 时的 SNI
reuse_hash()是否应该复用连接。当这个值相等时,则可以复用。

初次之外,还有一些有用的属性,包括:

  • get_proxy(): 是否使用代理连接
  • verify_cert(): 是否检查证书
  • verify_hostname: 是否检查 Hostname
  • connection_timeout(): 通过 connect 建立连接时的超时时间
  • total_connection_timeout(): 连接的总超时时间
  • matches_fd(): 判断某一 FD 是否和该连接匹配
  • get_tracer(): 获取该连接对应的 Tracer

Tracing 与 Tracer

trait Peer 中,定义了一种 Tracer 类型。这也是 Peer 用于追踪的手段。Tracer 需要实现 trace Tracing,负责在连接成功失败时被调用。定义如下:

/// The interface to trace the connection

pub trait Tracing: Send + Sync + std::fmt::Debug {

/// This method is called when successfully connected to a remote server

fn on_connected(&self);

/// This method is called when the connection is disconnected.

fn on_disconnected(&self);

/// A way to clone itself

fn boxed_clone(&self) -> Box<dyn Tracing>;

}

/// An object-safe version of Tracing object that can use Clone

#[derive(Debug)]

pub struct Tracer(pub Box<dyn Tracing>);

BasicPeer

在了解完 trait Peer 的定义后,我们来看一个简单的 Peer 实现:BasicPeerPingora 在一些简单的场景中会用到 BasicPeer,比如 TcpHealthCheck 和单元测试。这个实现也比较粗糙,目前只支持建立到 address 的非 TLS 连接。源码非常简单,基本就是把 field 填了一下。如下所示:

/// A simple TCP or TLS peer without many complicated settings.

#[derive(Debug, Clone)]

pub struct BasicPeer {

pub _address: SocketAddr,

pub sni: String,

pub options: PeerOptions,

}

impl BasicPeer {

/// Create a new [`BasicPeer`]

pub fn new(address: &str) -> Self {

BasicPeer {

_address: SocketAddr::Inet(address.parse().unwrap()), // TODO: check error, add support

// for UDS

sni: "".to_string(), // TODO: add support for SNI

options: PeerOptions::new(),

}

}

}

7 collapsed lines

impl Display for BasicPeer {

fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {

write!(f, "{:?}", self)

}

}

impl Peer for BasicPeer {

fn address(&self) -> &SocketAddr {

&self._address

}

fn tls(&self) -> bool {

!self.sni.is_empty()

}

fn bind_to(&self) -> Option<&InetSocketAddr> {

None

}

fn sni(&self) -> &str {

&self.sni

}

// TODO: change connection pool to accept u64 instead of String

fn reuse_hash(&self) -> u64 {

let mut hasher = AHasher::default();

self._address.hash(&mut hasher);

hasher.finish()

}

fn get_peer_options(&self) -> Option<&PeerOptions> {

Some(&self.options)

}

}

HttpPeer

看完了比较简单的,再来看看相对复杂一些的。HttpPeer,也就是我们在 Example 中用到的结构。相比 BasicPeer,它额外支持了 httpsSNI、代理、客户端证书,还有诸如 UDS(Unix Domain Socket) 的使用。

/// A peer representing the remote HTTP server to connect to

#[derive(Debug, Clone)]

pub struct HttpPeer {

pub _address: SocketAddr,

pub scheme: Scheme,

pub sni: String,

pub proxy: Option<Proxy>,

pub client_cert_key: Option<Arc<CertKey>>,

pub options: PeerOptions,

}

impl HttpPeer {

// These methods are pretty ad-hoc

pub fn is_tls(&self) -> bool {

match self.scheme {

Scheme::HTTP => false,

Scheme::HTTPS => true,

}

}

fn new_from_sockaddr(address: SocketAddr, tls: bool, sni: String) -> Self {

HttpPeer {

_address: address,

scheme: Scheme::from_tls_bool(tls),

sni,

proxy: None,

client_cert_key: None,

options: PeerOptions::new(),

}

}

/// Create a new [`HttpPeer`] with the given socket address and TLS settings.

pub fn new<A: ToInetSocketAddrs>(address: A, tls: bool, sni: String) -> Self {

let mut addrs_iter = address.to_socket_addrs().unwrap(); //TODO: handle error

let addr = addrs_iter.next().unwrap();

Self::new_from_sockaddr(SocketAddr::Inet(addr), tls, sni)

}

/// Create a new [`HttpPeer`] with the given path to Unix domain socket and TLS settings.

pub fn new_uds(path: &str, tls: bool, sni: String) -> Self {

let addr = SocketAddr::Unix(UnixSocketAddr::from_pathname(Path::new(path)).unwrap()); //TODO: handle error

Self::new_from_sockaddr(addr, tls, sni)

}

/// Create a new [`HttpPeer`] that uses a proxy to connect to the upstream IP and port

/// combination.

pub fn new_proxy(

next_hop: &str,

ip_addr: IpAddr,

port: u16,

tls: bool,

sni: &str,

headers: BTreeMap<String, Vec<u8>>,

) -> Self {

HttpPeer {

_address: SocketAddr::Inet(InetSocketAddr::new(ip_addr, port)),

scheme: Scheme::from_tls_bool(tls),

sni: sni.to_string(),

proxy: Some(Proxy {

next_hop: PathBuf::from(next_hop).into(),

host: ip_addr.to_string(),

port,

headers,

}),

client_cert_key: None,

options: PeerOptions::new(),

}

}

fn peer_hash(&self) -> u64 {

let mut hasher = AHasher::default();

self.hash(&mut hasher);

hasher.finish()

}

}

32 collapsed lines

impl Hash for HttpPeer {

fn hash<H: Hasher>(&self, state: &mut H) {

self._address.hash(state);

self.scheme.hash(state);

self.proxy.hash(state);

self.sni.hash(state);

// client cert serial

self.client_cert_key.hash(state);

// origin server cert verification

self.verify_cert().hash(state);

self.verify_hostname().hash(state);

self.alternative_cn().hash(state);

}

}

impl Display for HttpPeer {

fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {

write!(f, "addr: {}, scheme: {},", self._address, self.scheme)?;

if !self.sni.is_empty() {

write!(f, "sni: {},", self.sni)?;

}

if let Some(p) = self.proxy.as_ref() {

write!(f, "proxy: {p},")?;

}

if let Some(cert) = &self.client_cert_key {

write!(f, "client cert: {},", cert)?;

}

Ok(())

}

}

impl Peer for HttpPeer {

fn address(&self) -> &SocketAddr {

&self._address

}

fn tls(&self) -> bool {

self.is_tls()

}

fn sni(&self) -> &str {

&self.sni

}

// TODO: change connection pool to accept u64 instead of String

fn reuse_hash(&self) -> u64 {

self.peer_hash()

}

fn get_peer_options(&self) -> Option<&PeerOptions> {

Some(&self.options)

}

fn get_mut_peer_options(&mut self) -> Option<&mut PeerOptions> {

Some(&mut self.options)

}

fn get_proxy(&self) -> Option<&Proxy> {

self.proxy.as_ref()

}

fn matches_fd<V: AsRawFd>(&self, fd: V) -> bool {

if let Some(proxy) = self.get_proxy() {

proxy.next_hop.check_fd_match(fd)

} else {

self.address().check_fd_match(fd)

}

}

fn get_client_cert_key(&self) -> Option<&Arc<CertKey>> {

self.client_cert_key.as_ref()

}

fn get_tracer(&self) -> Option<Tracer> {

self.options.tracer.clone()

}

}

HttpPeer 支持三种构造形式:

  • [1]: new(),可以建立到某个 SocketAddr 的连接。
  • [2]: new_uds(),可以通过 Unix Domain Socket 的路径建立连接。
  • [3]: new_proxy(),则可以以一个 http proxy 作为跳板继续建立连接。

Proxy

HttpProxy 中使用的 Proxy 支持连接任意 host:portUDS 路径,并可以指定额外的 HTTP Header,定义如下:

/// The proxy settings to connect to the remote server, CONNECT only for now

#[derive(Debug, Hash, Clone)]

pub struct Proxy {

pub next_hop: Box<Path>, // for now this will be the path to the UDS

pub host: String, // the proxied host. Could be either IP addr or hostname.

pub port: u16, // the port to proxy to

pub headers: BTreeMap<String, Vec<u8>>, // the additional headers to add to CONNECT

}

12 collapsed lines

impl Display for Proxy {

fn fmt(&self, f: &mut Formatter) -> FmtResult {

write!(

f,

"next_hop: {}, host: {}, port: {}",

self.next_hop.display(),

self.host,

self.port

)

}

}