HTTP サーバーを書いてみよう!
async / .await
を使用してエコーサーバーを構築してみましょう!
最初に、rustup update nightly
で Rust の最新かつ最高のコピーを手に入れてください。
それが完了したら、cargo +nightly new 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」と見慣れた挨拶が表示されれば順調な証拠です。 おめでとうございます!
Rust で最初の非同期 Web サーバーを作成しました。
また、リクエスト URL、HTTP のバージョン、ヘッダー、その他のメタデータなどの情報を調べることも出来ます。例えば、次のようにリクエストの URL を出力できます。
#![allow(unused_variables)]
fn main() {
println!("Got request at {:?}", req.uri());
}
お気づきかな?すぐにレスポンスを返すため、リクエストを処理する際に非同期処理をまだ行ってないことに。 静的なメッセージを返すのではなく、Hyper の HTTP クライアントを使用して、ユーザーのリクエストを別の WEB サイトにプロキシしてみましょう。
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>>
を実装します。
.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 リクエストのプロキシに成功しました!!