LoginSignup
3
2

More than 3 years have passed since last update.

Rust で Web サーバーから HTTP リクエストを送信する(だけのことがなんだかむずかしかったのでなにかまちがっているのではないか)

Posted at

はじめに

  • Rust で Web サーバーの HTTP ハンドラの中から別のサービスへ HTTP リクエストを送信するだけのコードを書こうと思ったのですが意外にむずかしくてびっくりしました。
  • 一応動くものはできましたが、なんだか仰々しくなってしまい何か間違っている気がするので「普通はもっとこうするよ」とかご指摘あったら大歓迎の気持ちで公開してみます。

コード

Cargo.toml
[dependencies]
hyper = "0.12"
hyper-tls = "0.3.2"
futures = "0.1"
src/main.rs
use futures::future::Future;
use hyper::rt::{self, Stream};
use hyper::service::service_fn_ok;
use hyper::{Body, Client, Request, Response, Server};
use hyper_tls::HttpsConnector;

fn main() {
    let addr = ([127, 0, 0, 1], 3000).into();

    // HTTP ハンドラの登録
    let new_svc = || service_fn_ok(handler);

    // HTTP サーバーの作成と実行
    let server = Server::bind(&addr)
        .serve(new_svc)
        .map_err(|e| eprintln!("server error: {}", e));
    hyper::rt::run(server);
}

fn handler(_req: Request<Body>) -> Response<Body> {
    // 別スレッドで HTTP リクエストを送信する
    rt::spawn(rt::lazy(|| {
        // HTTPS 対応したクライアントを作る
        let https = HttpsConnector::new(4).unwrap();
        let client = Client::builder().build::<_, hyper::Body>(https);

        let uri = "http://httpbin.org/ip".parse().unwrap();

        // リクエストを実行する Future を作って返す
        client
            // get リクエスト
            .get(uri)
            // エラーだったらここで処理
            .map_err(|e| println!("request error: {}", e))
            // エラーでなければこれを実行
            .and_then(|res| {
                // レスポンスの Body の取得も Future で行う
                res.into_body()
                    .concat2()
                    // Body の取得エラーがあればここで処理
                    .map_err(|err| {
                        println!("Error: {}", err);
                    })
                    // Body が正常に取得できたら文字列にして表示
                    .map(|chunk| {
                        let v = chunk.to_vec();
                        let s = String::from_utf8_lossy(&v).to_string();
                        println!("HTTPS Body {}", s);
                    })
            })
    }));

    Response::new(Body::from(""))
}

解説

  • HTTP サーバーの実行は hyper の Example の通りです。
  • HTTP ハンドラ関数 handler では HTTP クライアントの hyper::Client を使って HTTP リクエストを送信しています。
  • hyper::Client のリクエストの実行方法はちょっと変わっていて、get() などでリクエストを行うと Future という処理のリストを返し、Future を非同期処理ライブラリが受け取ると Future に記載の処理が非同期で順番に実行される、という流れになります。
  • コードで見ると map_errmapand_then なんかが Future で処理を定義するメソッドで、ユーザーはここにリクエストに成功した場合や失敗した場合などの処理を実装していきます。

おわりに

  • 最初は reqwest 使ってサクッとできるだろうと思ったのですが、tokio::runtime が nested だぜ!だめだぜ!とか怒られてできませんでした。非同期な reqwest も検討しましたが hyper とあまり変わらないことになりそうだったのでやめました。
  • hyper にも非同期にもこだわっていないのでもっと簡単な方法あったら誰か教えてください!
3
2
3

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
3
2