update client example

This commit is contained in:
quininer 2019-05-22 23:57:14 +08:00
parent 3ffb736d5e
commit b8e3fcb79e
3 changed files with 65 additions and 59 deletions

View File

@ -2,11 +2,12 @@
name = "client" name = "client"
version = "0.1.0" version = "0.1.0"
authors = ["quininer <quininer@live.com>"] authors = ["quininer <quininer@live.com>"]
edition = "2018"
[dependencies] [dependencies]
webpki = "0.19" futures = { package = "futures-preview", version = "0.3.0-alpha.16", features = ["io-compat"] }
romio = "0.3.0-alpha.8"
structopt = "0.2"
tokio-rustls = { path = "../.." } tokio-rustls = { path = "../.." }
tokio = "0.1"
clap = "2"
webpki-roots = "0.16" webpki-roots = "0.16"
tokio-stdin-stdout = "0.1" tokio-stdin-stdout = "0.1"

View File

@ -1,74 +1,79 @@
extern crate clap; #![feature(async_await)]
extern crate tokio;
extern crate webpki;
extern crate webpki_roots;
extern crate tokio_rustls;
extern crate tokio_stdin_stdout;
use std::io;
use std::fs::File;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::io::BufReader; use std::io::BufReader;
use std::fs; use structopt::StructOpt;
use tokio::io; use romio::TcpStream;
use tokio::net::TcpStream; use futures::prelude::*;
use tokio::prelude::*; use futures::executor;
use clap::{ App, Arg }; use futures::compat::{ AsyncRead01CompatExt, AsyncWrite01CompatExt };
use tokio_rustls::{ TlsConnector, rustls::ClientConfig }; use tokio_rustls::{ TlsConnector, rustls::ClientConfig, webpki::DNSNameRef };
use tokio_stdin_stdout::{ stdin as tokio_stdin, stdout as tokio_stdout }; use tokio_stdin_stdout::{ stdin as tokio_stdin, stdout as tokio_stdout };
fn app() -> App<'static, 'static> {
App::new("client") #[derive(StructOpt)]
.about("tokio-rustls client example") struct Options {
.arg(Arg::with_name("host").value_name("HOST").required(true)) host: String,
.arg(Arg::with_name("port").short("p").long("port").value_name("PORT").help("port, default `443`"))
.arg(Arg::with_name("domain").short("d").long("domain").value_name("DOMAIN").help("domain")) /// port
.arg(Arg::with_name("cafile").short("c").long("cafile").value_name("FILE").help("CA certificate chain")) #[structopt(short="p", long="port", default_value="443")]
port: u16,
/// domain
#[structopt(short="d", long="domain")]
domain: Option<String>,
/// cafile
#[structopt(short="c", long="cafile", parse(from_os_str))]
cafile: Option<PathBuf>
} }
fn main() { fn main() -> io::Result<()> {
let matches = app().get_matches(); let options = Options::from_args();
let host = matches.value_of("host").unwrap(); let addr = (options.host.as_str(), options.port)
let port = matches.value_of("port") .to_socket_addrs()?
.map(|port| port.parse().unwrap()) .next()
.unwrap_or(443); .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
let domain = matches.value_of("domain").unwrap_or(host).to_owned(); let domain = options.domain.unwrap_or(options.host);
let cafile = matches.value_of("cafile"); let content = format!(
let text = format!("GET / HTTP/1.0\r\nHost: {}\r\n\r\n", domain); "GET / HTTP/1.0\r\nHost: {}\r\n\r\n",
domain
let addr = (host, port) );
.to_socket_addrs().unwrap()
.next().unwrap();
let mut config = ClientConfig::new(); let mut config = ClientConfig::new();
if let Some(cafile) = cafile { if let Some(cafile) = &options.cafile {
let mut pem = BufReader::new(fs::File::open(cafile).unwrap()); let mut pem = BufReader::new(File::open(cafile)?);
config.root_store.add_pem_file(&mut pem).unwrap(); config.root_store.add_pem_file(&mut pem)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))?;
} else { } else {
config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
} }
let config = TlsConnector::from(Arc::new(config)); let connector = TlsConnector::from(Arc::new(config));
let socket = TcpStream::connect(&addr); let fut = async {
let (stdin, stdout) = (tokio_stdin(0), tokio_stdout(0)); let stream = TcpStream::connect(&addr).await?;
let (mut stdin, mut stdout) = (tokio_stdin(0).compat(), tokio_stdout(0).compat());
let done = socket let domain = DNSNameRef::try_from_ascii_str(&domain)
.and_then(move |stream| { .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?;
let domain = webpki::DNSNameRef::try_from_ascii_str(&domain).unwrap();
config.connect(domain, stream)
})
.and_then(move |stream| io::write_all(stream, text))
.and_then(move |(stream, _)| {
let (r, w) = stream.split();
io::copy(r, stdout)
.map(drop)
.select2(io::copy(stdin, w).map(drop))
.map_err(|res| res.split().0)
})
.map(drop)
.map_err(|err| eprintln!("{:?}", err));
tokio::run(done); let mut stream = connector.connect(domain, stream).await?;
stream.write_all(content.as_bytes()).await?;
let (mut reader, mut writer) = stream.split();
future::try_join(
reader.copy_into(&mut stdout),
stdin.copy_into(&mut writer)
).await?;
Ok(())
};
executor::block_on(fut)
} }

View File

@ -7,5 +7,5 @@ edition = "2018"
[dependencies] [dependencies]
futures = { package = "futures-preview", version = "0.3.0-alpha.16" } futures = { package = "futures-preview", version = "0.3.0-alpha.16" }
romio = "0.3.0-alpha.8" romio = "0.3.0-alpha.8"
structopt = "*" structopt = "0.2"
tokio-rustls = { path = "../.." } tokio-rustls = { path = "../.." }