// 连接的封装与拨号:把「一条能读写的线」从 TCP / TLS / CONNECT 隧道三种
// 来手里统一起来。
//
// Connection 实现 `@io.Reader + @io.Writer`,所以它能直接交给 `Tls::client`
// 做 TLS 升级(隧道上 HTTPS 与直连 HTTPS 走同一条代码)。pushback 用
// `ReaderBuffer`:读状态行/头块的「多读一个字节」都留在缓冲里、不丢——
// 头阶段之后这些字节是**原始线上字节**,BodyReader(响应体)接管的正是它们:
// 它经 `conn.read` 把缓冲余量与后续字节都当作线上输入过一遍分帧解码,
// 自己另持一份输出缓冲(见 body_reader.mbt 的所有权注释)。
///|
/// 线路本体:裸 TCP 或 TLS(TLS 的内层可以是 TCP,也可以是隧道)。
priv enum Wire {
Plain(@socket.Tcp)
Secure(@tls.Tls)
}
///|
/// 一条已建立(或已升级)的连接。
priv struct Connection {
stream : Wire
buf : @io.ReaderBuffer
}
///|
/// 从一条裸 TCP 连接开始。
fn Connection::wrap(tcp : @socket.Tcp) -> Connection {
{ stream: Plain(tcp), buf: @io.ReaderBuffer::new(), }
}
///|
/// 在当前线路上叠 TLS(直连 HTTPS、或 CONNECT 隧道上的 HTTPS 都走这里)。
/// TLS 握手受外层的 `timeout` 约束——它在 `send_head` 的 attempt 闭包里发生。
/// 消费当前连接并给出一条新连接:升级点之前不会有多读的字节
/// (头块读取都发生在响应阶段),新缓冲从零开始。
async fn Connection::upgrade_tls(
self : Connection,
host : String,
) -> Connection {
let tls = @tls.Tls::client(self, host~)
{ stream: Secure(tls), buf: @io.ReaderBuffer::new(), }
}
///|
/// 关闭连接。TLS 的 close 同步完成;重复关闭由各层自行幂等。
fn Connection::close(self : Connection) -> Unit {
match self.stream {
Plain(tcp) => tcp.close()
Secure(tls) => tls.close()
}
}
///|
/// 写一段字节到线上(请求头块与请求体都走它)。
async fn Connection::write(self : Connection, data : &@io.Data) -> Unit {
match self.stream {
Plain(tcp) => tcp.write(data)
Secure(tls) => tls.write(data)
}
}
///|
/// `@io.Reader` 实现:内部缓冲即 pushback。
/// extend 声明让 read / read_until 可以按方法形式调用(否则是弃用告警的
/// 隐式提升;@io.Writer 的默认方法没有用到,不需要列)。
extend Connection with @io.Reader::{read, read_until}
///|
impl @io.Reader for Connection with fn _get_internal_buffer(self : Connection) -> @io.ReaderBuffer {
self.buf
}
///|
/// 从线路上直接读一段(0 = EOF)。默认方法(read / read_until / …)
/// 会先消费 pushback 再落到这里。
impl @io.Reader for Connection with fn _direct_read(
self : Connection,
dst : FixedArray[Byte],
offset~ : Int32,
max_len~ : Int32,
) -> Int32 {
match self.stream {
Plain(tcp) => tcp.read(dst, offset~, max_len~)
Secure(tls) => tls.read(dst, offset~, max_len~)
}
}
///|
/// `@io.Writer` 实现:只为满足 `Tls::client` 对「Reader + Writer」的要求,
/// 写路径不经过任何缓冲。
impl @io.Writer for Connection with fn write_once(
self : Connection,
data : Bytes,
offset~ : Int32,
len~ : Int32,
) -> Int32 {
match self.stream {
Plain(tcp) => tcp.write_once(data, offset~, len~)
Secure(tls) => tls.write_once(data, offset~, len~)
}
}
///|
/// 拨号到请求目标:直连(TCP / TLS)或经代理(CONNECT 隧道 + TLS)。
async fn dial(
target : Target,
proxy : @transport.ProxyEndpoint?,
) -> Connection raise @transport.TransportError {
match proxy {
None => {
let conn = connect_tcp(target.host, target.port)
if target.use_tls {
conn.upgrade_tls(target.host) catch {
error => raise @transport.TransportError::Network(error.to_string())
}
} else {
conn
}
}
Some(endpoint) => dial_via_proxy(target, endpoint)
}
}
///|
/// 建立 TCP 连接;DNS 解析失败、拒绝连接都归 `Network`。
async fn connect_tcp(
host : String,
port : Int32,
) -> Connection raise @transport.TransportError {
let tcp = @socket.Tcp::connect_to_host(host, port~) catch {
error => raise @transport.TransportError::Network(error.to_string())
}
Connection::wrap(tcp)
}
///|
/// 经代理拨号。SOCKS5 本轮是占位(docs/17 口径 3,握手字节属协议层,
/// 下一个提交补齐);CONNECT 代理的语义见契约文档 docs/09:
/// http 目标也走隧道,凭据只落在 CONNECT 请求上。
async fn dial_via_proxy(
target : Target,
endpoint : @transport.ProxyEndpoint,
) -> Connection raise @transport.TransportError {
let proxy = parse_target(endpoint.url)
if proxy.scheme == "socks5" {
raise @transport.TransportError::Unsupported(
"SOCKS5 代理即将支持(httpconn 第 1 期分两个提交落地),本次请求未发出",
)
}
let conn = connect_tcp(proxy.host, proxy.port)
errdefer conn.close()
// CONNECT 之后按目标是否 https 决定要不要叠 TLS;隧道本身总是明文建立的
let upgraded = establish_tunnel(conn, target, endpoint.authorization)
if target.use_tls {
upgraded.upgrade_tls(target.host) catch {
error => raise @transport.TransportError::Network(error.to_string())
}
} else {
upgraded
}
}
///|
/// 发 `CONNECT host:port` 并等到 2xx;此后这条连接就是到目标的裸管道。
/// 非 2xx 翻译成人话(文案口径见 docs/09 的错误表):
/// `407` 需要认证最常见,把状态码带出来比一句「网络失败」有用。
async fn establish_tunnel(
conn : Connection,
target : Target,
authorization : String?,
) -> Connection raise @transport.TransportError {
let authority = "\{target.host}:\{target.port}"
let headers = @httpproto.HeaderMap::new()
headers.append("Host", authority)
match authorization {
Some(value) => headers.append("Proxy-Authorization", value)
None => ()
}
let head = @httpproto.render_request_head("CONNECT", authority, headers) catch {
// 渲染失败是「请求构造不出来」,属发不出去一类
_ =>
raise @transport.TransportError::Unsupported("CONNECT 请求构造失败")
}
conn.write(head) catch {
error => raise @transport.TransportError::Network(error.to_string())
}
let status_line = match conn.read_line() {
Some(line) => line
None =>
raise @transport.TransportError::Network(
"代理关闭了连接,CONNECT 响应缺失",
)
}
let status = @httpproto.parse_status_line(status_line) catch {
_ =>
raise @transport.TransportError::Network(
"代理响应不是 HTTP 状态行",
)
}
if status.code < 200 || status.code >= 300 {
raise @transport.TransportError::Network(
"代理拒绝建立隧道:HTTP \{status.code} \{status.reason}",
)
}
// 读掉 CONNECT 响应的头块,让管道停在「干净的响应边界」上
discard_head_block(conn)
conn
}
///|
/// 逐行读掉一个头块(直到空行)。隧道握手与 1xx 跳过共用。
async fn discard_head_block(
conn : Connection,
) -> Unit raise @transport.TransportError {
while true {
let line = conn.read_line()
if line is Some("") {
return
}
if line is None {
raise @transport.TransportError::Network("连接在头块中途关闭")
}
}
}
///|
/// 从连接上读一行(到 CRLF 为止,分隔符被消费)。行读取都走它:
/// 默认 trait 方法的错误类型是宽的 `Error`,在这里收敛成 `TransportError`,
/// 调用方(隧道握手 / 响应头)才不用各自兜一圈。
async fn Connection::read_line(
self : Connection,
) -> String? raise @transport.TransportError {
self.read_until("\r\n") catch {
error => raise @transport.TransportError::Network(error.to_string())
}
}