응용: 간단한 HTTP 서버
async
/.await
를 사용하여 에코 서버를 만들어 보겠습니다.
본 예제를 시작하려면 rustup update stable
을 실행하여 Rust stable 1.39 이상을 유지하십시오. 일단 완료하면
cargo new async-await-echo
를 실행하여 새로운 프로젝트를 만들고 여십시오.
실행의 결과는 async-await-echo 폴더에 만들어 집니다.
Cargo.toml
파일에 몇몇 의존성을 추가해 봅시다 :
[dependencies]
# Hyper is an asynchronous HTTP library. We'll use it to power our HTTP
# server and to make HTTP requests.
hyper = "0.13.0"
# To setup some sort of runtime needed by Hyper, we will use the Tokio runtime.
tokio = { version = "0.2", features = ["full"] }
# (only for testing)
failure = "0.1.6"
reqwest = "0.9.24"
이제 의존성이 해결 되었으므로 이제 코딩을 시작하겠습니다. 다음의 imports를 추가합니다 :
use {
hyper::{
// Following functions are used by Hyper to handle a `Request`
// and returning a `Response` in an asynchronous manner by using a Future
service::{make_service_fn, service_fn},
// Miscellaneous types from Hyper for working with HTTP.
Body,
Client,
Request,
Response,
Server,
Uri,
},
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.
.serve(make_service_fn(|_| {
async {
{
Ok::<_, hyper::Error>(service_fn(serve_req))
}
}
}));
// 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.await {
eprintln!("server error: {}", e);
}
}
#[tokio::main]
async 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 using `await`;
run_server(addr).await;
}
지금 cargo run
하면, "Listening on http://127.0.0.1:3000" 이라는 메시지가 터미널에 나타납니다.
여러분이 선택한 브라우저에서 위 주소를 열면 "hello, world!"가 브라우저에 나타납니다.
축하합니다! 방금 Rust에서 첫 번째 비동기 웹 서버를 작성했습니다.
다음과 같은 정보(request URI, HTTP version, headers, and other metadata)가 포함 된 요청을 검사 할 수도 있습니다. 예를 들어, 다음과 같이 요청의 URI를 인쇄 할 수 있습니다.
println!("Got request at {:?}", req.uri());
우리가 요청을 처리 할 때 비동기적인 것을 아무 것도 아직 하고 있지 않은 것을 눈치 채셨나요?
-우리는 단지 즉시 응답합니다.
따라서 우리는 async fn
이 제공하는 유연성을 활용하지 않습니다.
정적 메시지를 반환하는 대신 Hyper의 HTTP 클라이언트를 사용하여 다른 웹 사이트에 사용자의 요청을 프록시 하도록 시도해 봅시다.
요청하려는 URL을 파싱하여 시작합니다.
let url_str = "http://www.rust-lang.org/en-US/";
let url = url_str.parse::<Uri>().expect("failed to parse URL");
그런 다음 새로운 hyper::Client
를 작성하고 이를 사용하여 GET
요청을 할 수 있습니다.
이는 응답을 사용자에게 반환합니다 :
let res = Client::new().get(url).await?;
// Return the result of the request directly to the user
println!("request finished-- returning response");
Ok(res)
Client::get
은 Future<Output = Result<Response<Body>>>
을 구현하는 hyper::client::ResponseFuture
를 반환합니다.
(또는 futures 0.1 버젼에서 Future<Item = Response<Body>, Error = Error>
).
우리가 그 future를 .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 요청을 프록시 했습니다.