Skip to content

Commit 885144b

Browse files
authored
Simplify client/server examples (#157)
1 parent be18b13 commit 885144b

File tree

8 files changed

+166
-141
lines changed

8 files changed

+166
-141
lines changed

.gitignore

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
# Generated by Cargo
2-
# will have compiled files and executables
3-
target/
4-
5-
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6-
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
1+
# Cargo
72
Cargo.lock
3+
debug/
4+
target/
85

9-
# These are backup files generated by rustfmt
6+
# rustfmt
107
**/*.rs.bk
118

9+
# IDE
1210
.idea
1311
.vscode
12+
13+
# macOS
14+
.DS_Store
15+
._*

examples/ca.cert

1.15 KB
Binary file not shown.

examples/client.rs

Lines changed: 73 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
use std::sync::Arc;
2-
use std::time::SystemTime;
1+
use std::{path::PathBuf, sync::Arc};
32

43
use futures::future;
5-
use h3_quinn::quinn;
6-
use rustls::{self, client::ServerCertVerified};
7-
use rustls::{Certificate, ServerName};
84
use structopt::StructOpt;
9-
use tokio::{self, io::AsyncWriteExt};
5+
use tokio::io::AsyncWriteExt;
6+
use tracing::{error, info};
107

11-
use h3_quinn::{self, quinn::crypto::rustls::Error};
8+
use h3_quinn::quinn;
129

1310
static ALPN: &[u8] = b"h3";
1411

1512
#[derive(StructOpt, Debug)]
1613
#[structopt(name = "server")]
1714
struct Opt {
18-
#[structopt(long)]
19-
pub insecure: bool,
15+
#[structopt(
16+
long,
17+
short,
18+
default_value = "examples/ca.cert",
19+
help = "Certificate of CA who issues the server certificate"
20+
)]
21+
pub ca: PathBuf,
2022

2123
#[structopt(name = "keylogfile", long)]
2224
pub key_log_file: bool,
@@ -31,76 +33,86 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3133
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
3234
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL)
3335
.with_writer(std::io::stderr)
36+
.with_max_level(tracing::Level::INFO)
3437
.init();
3538

3639
let opt = Opt::from_args();
3740

38-
let dest = opt.uri.parse::<http::Uri>()?;
41+
// DNS lookup
3942

40-
if dest.scheme() != Some(&http::uri::Scheme::HTTPS) {
41-
Err("destination scheme must be 'https'")?;
43+
let uri = opt.uri.parse::<http::Uri>()?;
44+
45+
if uri.scheme() != Some(&http::uri::Scheme::HTTPS) {
46+
Err("uri scheme must be 'https'")?;
4247
}
4348

44-
let auth = dest
45-
.authority()
46-
.ok_or("destination must have a host")?
47-
.clone();
49+
let auth = uri.authority().ok_or("uri must have a host")?.clone();
4850

4951
let port = auth.port_u16().unwrap_or(443);
5052

51-
// dns me!
5253
let addr = tokio::net::lookup_host((auth.host(), port))
5354
.await?
5455
.next()
5556
.ok_or("dns found no addresses")?;
5657

57-
eprintln!("DNS Lookup for {:?}: {:?}", dest, addr);
58+
info!("DNS lookup for {:?}: {:?}", uri, addr);
5859

59-
// quinn setup
60-
let tls_config_builder = rustls::ClientConfig::builder()
61-
.with_safe_default_cipher_suites()
62-
.with_safe_default_kx_groups()
63-
.with_protocol_versions(&[&rustls::version::TLS13])?;
64-
let mut tls_config = if !opt.insecure {
65-
let mut roots = rustls::RootCertStore::empty();
66-
match rustls_native_certs::load_native_certs() {
67-
Ok(certs) => {
68-
for cert in certs {
69-
if let Err(e) = roots.add(&rustls::Certificate(cert.0)) {
70-
eprintln!("failed to parse trust anchor: {}", e);
71-
}
60+
// create quinn client endpoint
61+
62+
// load CA certificates stored in the system
63+
let mut roots = rustls::RootCertStore::empty();
64+
match rustls_native_certs::load_native_certs() {
65+
Ok(certs) => {
66+
for cert in certs {
67+
if let Err(e) = roots.add(&rustls::Certificate(cert.0)) {
68+
error!("failed to parse trust anchor: {}", e);
7269
}
7370
}
74-
Err(e) => {
75-
eprintln!("couldn't load any default trust roots: {}", e);
76-
}
77-
};
78-
tls_config_builder
79-
.with_root_certificates(roots)
80-
.with_no_client_auth()
81-
} else {
82-
tls_config_builder
83-
.with_custom_certificate_verifier(Arc::new(YesVerifier))
84-
.with_no_client_auth()
71+
}
72+
Err(e) => {
73+
error!("couldn't load any default trust roots: {}", e);
74+
}
8575
};
76+
77+
// load certificate of CA who issues the server certificate
78+
// NOTE that this should be used for dev only
79+
if let Err(e) = roots.add(&rustls::Certificate(std::fs::read(opt.ca)?)) {
80+
error!("failed to parse trust anchor: {}", e);
81+
}
82+
83+
let mut tls_config = rustls::ClientConfig::builder()
84+
.with_safe_default_cipher_suites()
85+
.with_safe_default_kx_groups()
86+
.with_protocol_versions(&[&rustls::version::TLS13])?
87+
.with_root_certificates(roots)
88+
.with_no_client_auth();
89+
8690
tls_config.enable_early_data = true;
8791
tls_config.alpn_protocols = vec![ALPN.into()];
8892

93+
// optional debugging support
8994
if opt.key_log_file {
9095
// Write all Keys to a file if SSLKEYLOGFILE is set
9196
// WARNING, we enable this for the example, you should think carefully about enabling in your own code
9297
tls_config.key_log = Arc::new(rustls::KeyLogFile::new());
9398
}
9499

95-
let client_config = quinn::ClientConfig::new(Arc::new(tls_config));
96-
97100
let mut client_endpoint = h3_quinn::quinn::Endpoint::client("[::]:0".parse().unwrap())?;
101+
102+
let client_config = quinn::ClientConfig::new(Arc::new(tls_config));
98103
client_endpoint.set_default_client_config(client_config);
99-
let quinn_conn = h3_quinn::Connection::new(client_endpoint.connect(addr, auth.host())?.await?);
100104

101-
eprintln!("QUIC connected ...");
105+
let conn = client_endpoint.connect(addr, auth.host())?.await?;
106+
107+
info!("QUIC connection established");
108+
109+
// create h3 client
110+
111+
// h3 is designed to work with different QUIC implementations via
112+
// a generic interface, that is, the [`quic::Connection`] trait.
113+
// h3_quinn implements the trait w/ quinn to make it work with h3.
114+
let quinn_conn = h3_quinn::Connection::new(conn);
102115

103-
// generic h3
104116
let (mut driver, mut send_request) = h3::client::new(quinn_conn).await?;
105117

106118
let drive = async move {
@@ -115,48 +127,41 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
115127
// So we "move" it.
116128
// vvvv
117129
let request = async move {
118-
eprintln!("Sending request ...");
130+
info!("sending request ...");
119131

120-
let req = http::Request::builder().uri(dest).body(())?;
132+
let req = http::Request::builder().uri(uri).body(())?;
121133

134+
// sending request results in a bidirectional stream,
135+
// which is also used for receiving response
122136
let mut stream = send_request.send_request(req).await?;
137+
138+
// finish on the sending side
123139
stream.finish().await?;
124140

125-
eprintln!("Receiving response ...");
141+
info!("receiving response ...");
142+
126143
let resp = stream.recv_response().await?;
127144

128-
eprintln!("Response: {:?} {}", resp.version(), resp.status());
129-
eprintln!("Headers: {:#?}", resp.headers());
145+
info!("response: {:?} {}", resp.version(), resp.status());
146+
info!("headers: {:#?}", resp.headers());
130147

148+
// `recv_data()` must be called after `recv_response()` for
149+
// receiving potential response body
131150
while let Some(mut chunk) = stream.recv_data().await? {
132151
let mut out = tokio::io::stdout();
133152
out.write_all_buf(&mut chunk).await?;
134153
out.flush().await?;
135154
}
155+
136156
Ok::<_, Box<dyn std::error::Error>>(())
137157
};
138158

139159
let (req_res, drive_res) = tokio::join!(request, drive);
140160
req_res?;
141161
drive_res?;
142162

163+
// wait for the connection to be closed before exiting
143164
client_endpoint.wait_idle().await;
144165

145166
Ok(())
146167
}
147-
148-
struct YesVerifier;
149-
150-
impl rustls::client::ServerCertVerifier for YesVerifier {
151-
fn verify_server_cert(
152-
&self,
153-
_end_entity: &Certificate,
154-
_intermediates: &[Certificate],
155-
_server_name: &ServerName,
156-
_scts: &mut dyn Iterator<Item = &[u8]>,
157-
_ocsp_response: &[u8],
158-
_now: SystemTime,
159-
) -> Result<ServerCertVerified, Error> {
160-
Ok(ServerCertVerified::assertion())
161-
}
162-
}

examples/readme.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ This also generates a self-signed certificate for encryption.
1515
To start the example client you can run following command:
1616

1717
```bash
18-
> cargo run --example client -- https://localhost:4433 --insecure=true
18+
> cargo run --example client -- https://localhost:4433
1919
```
2020

2121
This sends an HTTP request to the server.
22-
The `--insecure=true` allows the client to accept invalid certificates like the self-signed, which the server has created.
2322

2423
## Add some content to the Server
2524
So that the server responds something you can provide a directory with content files.
@@ -31,7 +30,7 @@ So that the server responds something you can provide a directory with content f
3130
To start the client simply put the file name behind the URI:
3231

3332
```bash
34-
> cargo run --example client -- https://localhost:4433/index.html --insecure=true
33+
> cargo run --example client -- https://localhost:4433/index.html
3534
```
3635

3736
## Test against the Browser

examples/server.cert

996 Bytes
Binary file not shown.

examples/server.key

1.16 KB
Binary file not shown.

0 commit comments

Comments
 (0)