- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
gitee 传送门 github 传送门 。
代理端和代理服务端之间可用自有格式来实现多路复用以减少连接的建立断开的开销,目前暂未实现代理服务端.
使用 Commander 对命令行的的数据处理,如-p 8090,-b 127.0.0.1,完整的命令行如wmproxy -p 8090,则可在8090端口上实现http及https的转发,代码示例 。
let command = Commander::new()
.version(&env!("CARGO_PKG_VERSION").to_string())
.usage("-b 127.0.0.1 -p 8090")
.usage_desc("use http proxy")
.option_list(
"-f, --flag [value]",
"可兼容的方法, 如http https socks5",
None,
)
.option_int("-p, --port [value]", "listen port", Some(8090))
.option_str(
"-b, --bind [value]",
"bind addr",
Some("0.0.0.0".to_string()),
)
.parse_env_or_exit();
let listen_port: u16 = command.get_int("p").unwrap() as u16;
let listen_host = command.get_str("b").unwrap();
启动通过tokio的异步协议进行数据的处理,逻辑均在 tokio::spawn 的异步函数中,所有针对句柄数据的读取写入均由异步完成,从而实现高效率的处理.
while let Ok((mut inbound, _)) = listener.accept().await {
tokio::spawn(async move {
// tcp的连接被移动到该协程中,我们只要专注的处理该stream即可
})
}
如果该代理信息配置支持http/https则会尝试进行http解析,代码实现在 proxy.rs 中的 process 方法, 。
pub async fn process(mut inbound: TcpStream) -> ProxyResult<()> {
let request = webparse::Request::new();
// 通过该方法解析标头是否合法, 若是partial(部分)则继续读数据
// 若解析失败, 则表示非http协议能处理, 则抛出错误
match request.parse_buffer(&mut buffer.clone()) {
}
}
该方法会循环的读取客户端的内容,如果内容为 。
GET / HTTP/1.1\r\nHost: wwww.baidu.com\r\n\r\n
这表示该请求为普通的http代理,我们解析完HTTP的头文件信息,得出包含的头信息,如果无法解析完整的地址(域名加端口或者ip加端口),则返回错误,无法处理该http信息.
注意:客户端和服务端之前可能会存在大数据上传下载的情况,超过百兆数据的上传下载,所以我们为了减少序列化带来的性能损失和保证在低内存能正确运行,不做http的完整解析,仅仅只处理http头信息.
curl测试 。
export http_proxy=http://127.0.0.1:8090
curl http://www.baidu.com -I
可以正常的返回 。
HTTP/1.1 200 OK...
https处理是在http的基础在在额外解析connect协议来实现, 代理是客户端优先给代理发送connect协议,比如访问https://www.baidu.com那么先优先发如下消息.
CONNECT www.baidu.com:443 HTTP/1.1\r\n
Host: www.baidu.com:443\r\n\r\n
如果收到HTTP的CONNECT的方法则表示他是https的代理协议,那么此时对PATH提示的地址进行连接,连接成功后只需对该连接和客户端做双向绑定即可实现HTTPS代理协议.
curl测试 。
export https_proxy=http://127.0.0.1:8090
curl https://www.baidu.com -I
可以正常的返回两次,因为在connect的时候要求代理返回一次数据,另一次是https服务器返回,故而显示g 。
HTTP/1.1 200 OK
HTTP/1.1 200 OK
...
socks5由 rfc1928 进行定义 代码实现在 socks5.rs 中的 process 方法实现 因为在处理 socks5 之前可能进行过http的尝试,所以socket中的内容已经被读出了一部分,在处理时则带上了 Option<BinaryMut> ,表示预读的内容。 在socks5中通常需要预读一个字节来获取后续的长度,比如NMethod,或者用户名长度等,所以我们定义了函数 。
/// 读取至少长度为size的大小的字节数, 如果足够则返回Ok(())
pub async fn read_len<T>(stream: &mut T, buffer: &mut BinaryMut, size: usize) -> ProxyResult<()>
where
T: AsyncRead + Unpin {
}
这里的stream用的是泛型,只要具有异步读的类型都可以 。
保证已读内容须不少于多少字节数,然后再进行数据的预处理。 根据我们是否传用用户密码信息来确定socks5的验证方式,如果我们传入了用户密码,如果客户端不支持2的验证方式,则返回(0xFF)表示无验证方法.
curl http://www.baidu.com --socks5 127.0.0.1:8090
## curl: (97) No authentication method was acceptable.
验证成功或者无需验证后 。
双向通道建立后,客户端已和服务器能正常的TCP操作,包括Http/Https/Websocket/自定义tcp信息,代理直到一方关闭则正常后续关闭.
这里主要说明如何多协议兼容处理代理协议。以下定义的Continue协议包含了一个已读的字节表和当前的Tcp连接.
pub enum ProxyError {
/// 该错误发生协议不可被解析,则尝试下一个协议
Continue((Option<BinaryMut>, TcpStream)),
}
例如在http里协议解决头失败, 。
// 此处clone为浅拷贝,不确定是否一定能解析成功,不能影响偏移
match request.parse_buffer(&mut buffer.clone()) {
Err(_) => {
return Err(ProxyError::Continue((Some(buffer), inbound)));
}
}
则返回当前已读的buffer和tcp连接,且游标为初始位置,buffer并未被读取过。下个解析器可以拿到完整的数据进行解析.
最后此篇关于用Rust手把手编写一个Proxy(代理),动工的文章就讲到这里了,如果你想了解更多关于用Rust手把手编写一个Proxy(代理),动工的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是一名优秀的程序员,十分优秀!