Применение: HTTP сервер
Давайте используем async
/.await
для создания echo-сервера!
Для начала, запустите rustup update nightly
чтобы
быть уверенным, что используете последнюю версию Rust - мы
работаем с передовыми функциями, так что надо быть
обновлённым. Когда вы закончите это, создайте новый проект с
помощью cargo +nightly new async-await-echo
и
откройте созданную директорию async-await-echo
.
Добавим некоторые зависимости в файл Cargo.toml
:
[dependencies]
# The latest version of the "futures" library, which has lots of utilities
# for writing async code. Enable the "compat" feature to include the
# functions for using futures 0.3 and async/await with the Hyper library,
# which use futures 0.1.
futures-preview = { version = "=0.3.0-alpha.17", features = ["compat"] }
# Hyper is an asynchronous HTTP library. We'll use it to power our HTTP
# server and to make HTTP requests.
hyper = "0.12.9"
Теперь, когда у нас есть свои зависимости, давайте начнём писать код. Вот список зависимостей, которые необходимо добавить:
# #![allow(unused_variables)] #fn main() { use { hyper::{ // Miscellaneous types from Hyper for working with HTTP. Body, Client, Request, Response, Server, Uri, // This function turns a closure which returns a future into an // implementation of the the Hyper `Service` trait, which is an // asynchronous function from a generic `Request` to a `Response`. service::service_fn, // A function which runs a future to completion using the Hyper runtime. rt::run, }, futures::{ // Extension trait for futures 0.1 futures, adding the `.compat()` method // which allows us to use `.await` on 0.1 futures. compat::Future01CompatExt, // Extension traits providing additional methods on futures. // `FutureExt` adds methods that work for all futures, whereas // `TryFutureExt` adds methods to futures that return `Result` types. future::{FutureExt, TryFutureExt}, }, std::net::SocketAddr, }; #}
Как только закончим с импортами, мы можем собрать вместе весь шаблонный код, который позволит обрабатывать запросы:
async fn serve_req(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> { // Always return successfully with a response containing a body with // a friendly greeting ;) Ok(Response::new(Body::from("hello, world!"))) } async fn run_server(addr: SocketAddr) { println!("Listening on http://{}", addr); // Create a server bound on the provided address let serve_future = Server::bind(&addr) // Serve requests using our `async serve_req` function. // `serve` takes a closure which returns a type implementing the // `Service` trait. `service_fn` returns a value implementing the // `Service` trait, and accepts a closure which goes from request // to a future of the response. To use our `serve_req` function with // Hyper, we have to box it and put it in a compatability // wrapper to go from a futures 0.3 future (the kind returned by // `async fn`) to a futures 0.1 future (the kind used by Hyper). .serve(|| service_fn(|req| serve_req(req).boxed().compat())); // Wait for the server to complete serving or exit with an error. // If an error occurred, print it to stderr. if let Err(e) = serve_future.compat().await { eprintln!("server error: {}", e); } } fn main() { // Set the address to run our socket on. let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); // Call our `run_server` function, which returns a future. // As with every `async fn`, for `run_server` to do anything, // the returned future needs to be run. Additionally, // we need to convert the returned future from a futures 0.3 future into a // futures 0.1 future. let futures_03_future = run_server(addr); let futures_01_future = futures_03_future.unit_error().boxed().compat(); // Finally, we can run the future to completion using the `run` function // provided by Hyper. run(futures_01_future); }
Если вы сейчас запустите cargo run
, в консоли вы
увидите сообщение "Listening on http://127.0.0.1:3000". Если вы
откроете URL в вашем любимом браузере, вы увидите как в нём
отобразится "hello, world!". Поздравляем! Вы только что написали
свой первый асинхронный web-сервер на Rust.
Вы также можете посмотреть сам запрос, который содержит такую информацию, как URI, версию HTTP, заголовки и другие метаданные. Например, мы можем вывести URI запроса следующим образом:
# #![allow(unused_variables)] #fn main() { println!("Got request at {:?}", req.uri()); #}
Вы могли заметить, что мы до сих пор не делали ничего
асинхронного для обработки запроса - мы только незамедлительно
ответили на него, мы не пользуемся гибкостью, которую нам даёт
async fn
. Вместо этого, мы только возвращаем
статическое сообщение. Давайте попробуем проксировать
пользовательский запрос на другой web-сайт используя
HTTP-клиент Hyper'а.
Мы начнём с парсинга URL, который мы хотим запросить:
# #![allow(unused_variables)] #fn main() { let url_str = "http://www.rust-lang.org/en-US/"; let url = url_str.parse::<Uri>().expect("failed to parse URL"); #}
Затем мы создадим новый hyper::Client
и
используем его для создания GET
запроса, который
вернём пользователю ответ:
# #![allow(unused_variables)] #fn main() { let res = Client::new().get(url).compat().await; // Return the result of the request directly to the user println!("request finished-- returning response"); res #}
Client::get
возвращает
hyper::client::FutureResponse
, который реализует
Future<Output = Result<Response, Error>>
(или Future<Item = Response, Error = Error>
в
терминах futures
0.1). Когда мы разрешаем (.await
)
футуру, отправляется HTTP-запрос, текущая задача
приостанавливается и становится в очередь, чтобы продолжить
работу после получения ответа.
Если вы сейчас запустите cargo run
и откроете
http://127.0.0.1:3000/foo
в браузере, вы увидите
домашнюю страницу Rust, а в консоли следующий вывод:
Listening on http://127.0.0.1:3000
Got request at /foo
making request to http://www.rust-lang.org/en-US/
request finished-- returning response
Поздравляем! Вы только что проксировали HTTP запрос.