LoginSignup
6
4

More than 5 years have passed since last update.

Rust で並列HTTP Request (非同期IO版)

Posted at

前回 はスレッドで並列HTTP Request を実装しましたが、 hyper が v0.11.0 で非同期IOに対応したので、非同期IO版も書いてみました。

extern crate futures;
extern crate hyper;
extern crate tokio_core;

use std::io::{ self, Write };
use std::time::{ Duration, SystemTime };
use futures::{ Future };
use futures::future::{ join_all };
use hyper::Client;
use hyper::client::{ HttpConnector };
use tokio_core::reactor::Core;

trait DurationExtended {
    fn to_seconds(&self) -> f64;
}

impl DurationExtended for Duration {
    fn to_seconds(&self) -> f64 {
        let nanos = self.subsec_nanos() as u64;
        let secs  = self.as_secs();
        ((1000_000_000 * secs + nanos) as f64) / 1000_000_000.
    }
}

fn http_request_job(client: &Client<HttpConnector>,
                    index: usize,
                    url: String) -> Box<Future<Item=(), Error=()>> {
    let uri: hyper::Uri = url.parse().unwrap();
    let time = SystemTime::now();

    let job = client.get(uri).then(move |result| -> Result<(), ()> {
        let duration = time.elapsed().map(|t| t.to_seconds()).unwrap();
        match result {
            Ok(response) => {
                println!("{:2}: {:5.3}: {} => {}", 
                         index, duration, url, response.status());
            },
            Err(error) => {
                let _ = writeln!(&mut io::stderr(), "{:2}: Error: {}", index, error);
            }
        }
        Ok(())
    });
    Box::new(job)
}

fn main() {
    let gen_url = |q: &str| -> String { format!("http://www.google.com/search?q={}", q) };
    let urls: Vec<String> =
        vec![].into_iter()
            .chain(vec!["clojure", "haskell", "ocaml", "rust", "scala"]
                        .iter().map(|q| gen_url(q)))
            .chain(vec!["http://127.0.0.1:5000/".to_owned()].into_iter())
            .chain(vec!["javascript", "python", "perl", "php", "ruby"]
                        .iter().map(|q| gen_url(q)))
            .collect();

    let mut core = Core::new().unwrap();
    let client   = Client::new(&core.handle());

    let jobs = urls.into_iter().enumerate()
        .map(|(i, url)| http_request_job(&client, i + 1, url));

    core.run(join_all(jobs)).unwrap();
}

//  To check parallel http requests, prepare a long time request:
//  $ rackup -s webrick --port 5000 -b 'run(proc { |env| sleep 5; ["200", {"Content-Type" => "text/html"}, ["Hello World"]] })'

// $ cargo run
// ...
//  1: 0.086: http://www.google.com/search?q=clojure => 302 Found
//  4: 0.086: http://www.google.com/search?q=rust => 302 Found
//  3: 0.087: http://www.google.com/search?q=ocaml => 302 Found
//  7: 0.088: http://www.google.com/search?q=javascript => 302 Found
// 10: 0.089: http://www.google.com/search?q=php => 302 Found
//  2: 0.091: http://www.google.com/search?q=haskell => 302 Found
// 11: 0.091: http://www.google.com/search?q=ruby => 302 Found
//  5: 0.092: http://www.google.com/search?q=scala => 302 Found
//  9: 0.092: http://www.google.com/search?q=perl => 302 Found
//  8: 0.112: http://www.google.com/search?q=python => 302 Found
//  6: 5.012: http://127.0.0.1:5000/ => 200 OK
Cargo.toml
[package]
name = "http_request2"
version = "0.1.0"

[dependencies]
futures = "*"
hyper = "0.11.0"
tokio-core = "*"
6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4