reqwest
在tokio中的HTTP客户端,可以使用reqwest。
reqwest的feature中,有native-tls
,可以直接支持证书校验。
在Cargo.toml中加入依赖:
[dependencies]
tokio = { version = "1.36.0", features = ["full"] }
reqwest = { version = "0.11", features = ["native-tls"] }
即可以在初始化HTTP Client的时候,加入证书。方法是把证书读入一个Identity中,之后使用Client::Builder的identity函数。
如:
fn client_new(path: &str) -> Client {let p12_data = fs::read(path).expect("Failed to read certificate file"); let identity = Identity::from_pkcs12_der(&p12_data, "") .expect("Failed to create identity from PKCS12"); let client_builder = Client::builder() .identity(identity) client_builder.build().expect("Failed to create HTTP client")
}
另外,Client::Builder还有一个danger_accept_invalid_certs
函数,可以控制在服务端证书有问题的情况下,是否接受。
如以下代码,就忽略了服务端证书错误:
let client = client_builder .identity(identity) .danger_accept_invalid_certs(true).build().expect("Failed to create HTTP client")
使用Client的get方法:
async fn get_request(client: &Client, url: &str) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> { let response = client.get(url).send().await?; if !response.status().is_success() { return Err(format!("HTTP get failed with status: {}", response.status()).into()); }Ok(response)
}```使用Client的post方法:
```rust
async fn post_request(client: &Client,url: &str, data: serde_json::Value,
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> { let response = client.post(url).json(&data).send().await?; if !response.status().is_success() { return Err(format!("HTTP post failed with status: {}", response.status()).into()); } Ok(response)
}
hyper作为Web服务端
hyper是Rust中开发Web服务的crate。
创建一个SocketAddr,之后使用hyper::Server
的bind方法绑定到地址上,之后即可以在之上挂接一个回调函数来处理Web请求。
如:
async fn start_http(port: u16) {let addr = SocketAddr::from(([0, 0, 0, 0], port)); let make_svc = make_service_fn(|conn: &AddrStream| { let client_addr = conn.remote_addr(); async move { Ok::<_, Infallible>(service_fn(move |req| handle_request(req, client_addr))) }
});let server = Server::bind(&addr).serve(make_svc);server.await.expect("Failed to start HTTP server");
}
openssl工具
我们可以使用OpenSSL套件中的openssl命令,生成测试证书。
如:
# 创建 CA
openssl req -x509 -newkey rsa:4096 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes -subj "/CN=MyCA" # 创建服务器证书
openssl req -newkey rsa:4096 -keyout server_key.pem -out server_csr.pem -nodes -subj "/CN=localhost"
openssl x509 -req -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out server_cert.pem
-days 365 # 创建客户端证书
openssl req -newkey rsa:4096 -keyout client_key.pem -out client_csr.pem -nodes -subj "/CN=TestClient"
openssl x509 -req -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out client_cert.pem
-days 365
还可以把证书、key以及ca导出到一个p12格式的证书文件中:
# 服务端P12证书
openssl pkcs12 -export -in server_cert.pem -inkey server_key.pem -CAfile ca_cert.pem -out server.p12# 客户端测试证书
openssl pkcs12 -export -in client_cert.pem -inkey client_key.pem -CAfile ca_cert.pem -out client.p12
Rust的openssl::Pkcs12
Rust中也有openssl的crate,可以使用openssl,解析P12格式的文件,取出其中的证书、key以及ca:
let p12_data = std::fs::read("cert.p12").expect("Failed to read P12 certificate");
let p12 = Pkcs12::from_der(&p12_data).expect("Failed to load P12 certificate");
let p12_parsed = p12.parse2("").expect("Failed to parse P12 certificate"); let cert_chain = if let Some(cert) = p12_parsed.cert { vec![Certificate( cert.to_der().expect("Failed to serialize certificate"), )]
} else { panic!("No certificate found in P12");
}; let private_key = if let Some(pkey) = p12_parsed.pkey { PrivateKey( pkey.private_key_to_der() .expect("Failed to load private key"), )
} else { panic!("No private key found in P12");
}; let mut roots = RootCertStore::empty();
if let Some(ca) = p12_parsed.ca { for cert in ca { roots.add(&Certificate( cert.to_der().expect("Failed to serialize certificate"), )) .expect("Failed to add certificate"); }
}
hyper-rustls
前面提到的hyper,没有支持HTTPS。我们可以使用hyper-rustls,来支持HTTPS的服务。
首先,使用Pcs12导出的证书、key以及ca链,生成ServerConfig:
let client_auth = AllowAnyAuthenticatedClient::new(roots); let server_config = ServerConfig::builder() .with_safe_defaults() .with_client_cert_verifier(Arc::new(client_auth)) .with_single_cert(cert_chain, private_key) .expect("Failed to set certificate")
使hyper应用Rustls的ServerConfig:
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
let acceptor = TlsAcceptor::from(Arc::new(server_config));
let make_svc = make_service_fn(|conn: &TlsStream<TcpStream>| { let client_addr = conn.get_ref().0.peer_addr().unwrap(); async move { Ok::<_, Infallible>(service_fn(move |req| handle_request(req, client_addr))) }
});let incoming_tls_stream = futures_util::stream::unfold(listener, move |listener| { let acceptor = acceptor.clone(); async move { match listener.accept().await { Ok((stream, _)) => { let acceptor = acceptor.clone(); match acceptor.accept(stream).await { Ok(tls_stream) => Some((Ok(tls_stream), listener)), Err(e) => Some(( Err(std::io::Error::new(std::io::ErrorKind::Other, e)), listener, )), } } Err(e) => Some((Err(e), listener)), } }
});let server = Server::builder(from_stream(incoming_tls_stream)).serve(make_svc);
info!("HTTPS running on https://{}", addr);
server.await.expect("Failed to run HTTPS server");
主要的逻辑,就是使用hyper的Server::builder
从TLS的Stream,来执行serve函数,挂接make_svc过程。
上面的incoming_tls_stream
,在失败的情况下会退出。可以把上面的过程,包在一个循环中,出错就只是打印错误,之后继续等待连接。
如:
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
let incoming_tls_stream = futures_util::stream::unfold(listener, move |listener| { let acceptor = acceptor.clone(); async move { loop { match listener.accept().await { Ok((stream, _)) => match acceptor.accept(stream).await { Ok(tls_stream) => { return Some(( Ok::<_, Box<dyn std::error::Error + Send + Sync>>(tls_stream), listener, )); }// TLS层接受连接失败,则报错,之后继续循环 Err(e) => { warn!("tls accept error: {}", e); continue; } },// TCP层接受连接失败,也报错,之后继续循环Err(e) => { warn!("tcp accept error: {}", e); continue; } } } }
});