Hyper
Rust向けの低レベル高性能HTTPライブラリ。HTTP/1.1、HTTP/2完全サポート、非同期I/O最適化。他のライブラリやフレームワークの基盤として設計され、reqwestやwarpなど多くのライブラリで採用。カスタマイズ性と性能を重視する開発者向け。
GitHub概要
スター15,387
ウォッチ174
フォーク1,659
作成日:2014年8月30日
言語:Rust
ライセンス:MIT License
トピックス
httphyperrust
スター履歴
データ取得日時: 2025/7/18 01:38
ライブラリ
Hyper
概要
Hyperは「Rust向けの高速で正確なHTTPライブラリ」として開発された、低レベルなHTTPクライアント・サーバーライブラリです。速度、正確性、非同期設計に重点を置き、HTTP/1とHTTP/2の両方をサポート。フル機能のフレームワークではなく、HTTPアプリケーションの構成要素として設計されており、より高レベルなライブラリ(reqwest、axum、warpなど)の基盤として機能します。Rustの型システムを活用してメモリとスレッドの安全性を確保し、最小限のオーバーヘッドで最高のパフォーマンスを実現します。
詳細
Hyper 2025年版は、Rustエコシステムにおける事実上の標準HTTPライブラリとして確固たる地位を築いています。非同期I/O(async/await)を中心とした設計により、高い同時接続数を効率的に処理可能。モジュール式のフィーチャーシステムを採用し、必要なコンポーネントのみを選択的に組み込むことでコンパイル時間とバイナリサイズを最適化。Service traitを基盤としたアーキテクチャにより、ミドルウェアパターンやカスタマイズ可能なHTTP処理パイプラインを実現し、企業レベルの堅牢なHTTPアプリケーション構築をサポートします。
主な特徴
- 高速パフォーマンス: 最小限のオーバーヘッドで最適化された実行速度
- HTTP仕様準拠: HTTP/1.1とHTTP/2の正確で厳密な実装
- 非同期ファースト: Rustのasync/awaitエコシステムとのシームレス統合
- モジュール設計: フィーチャーフラグによる選択的コンポーネント導入
- Service trait: 柔軟なミドルウェアパターンとリクエスト処理システム
- メモリ安全性: Rustの型システムによる堅牢なメモリ・スレッド安全性
メリット・デメリット
メリット
- Rustエコシステムにおける最高クラスのHTTPパフォーマンス実現
- HTTP仕様への厳密な準拠による堅牢で信頼性の高い通信
- フィーチャーフラグシステムによる最適化されたバイナリサイズ
- 非同期処理による高いスケーラビリティと効率的なリソース利用
- Service traitによる柔軟なアーキテクチャとミドルウェア対応
- Rust型システムによるコンパイル時エラー検出と安全性保証
デメリット
- 低レベルライブラリのため高度なRustと非同期プログラミング知識が必要
- 直接利用には煩雑な実装が必要(reqwest等の高レベルライブラリ推奨)
- フル機能フレームワークではないため追加ライブラリとの組み合わせが必要
- C FFI(Foreign Function Interface)が不安定でSemVer保証対象外
- 初心者には学習コストが高くドキュメントが技術的
- 他言語からの移行時に独特のRustパターンへの習熟が必要
参考ページ
書き方の例
依存関係の設定とプロジェクト準備
# Cargo.toml
[dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
bytes = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# プロジェクト作成と依存関係の追加
cargo new hyper-client-example
cd hyper-client-example
# 依存関係の確認
cargo check
基本的なHTTPクライアント実装
use hyper::{Client, Request, Body, Method, Uri};
use hyper::client::HttpConnector;
use http_body_util::Empty;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// HTTPクライアントの作成
let client = Client::new();
// 基本的なGETリクエスト
let uri: Uri = "http://httpbin.org/get".parse()?;
let response = client.get(uri).await?;
println!("Status: {}", response.status());
println!("Headers: {:#?}", response.headers());
// レスポンスボディの読み取り
let body_bytes = hyper::body::to_bytes(response.into_body()).await?;
let body_str = String::from_utf8(body_bytes.to_vec())?;
println!("Body: {}", body_str);
Ok(())
}
// カスタムリクエストビルダー
async fn custom_get_request() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::new();
let request = Request::builder()
.method(Method::GET)
.uri("https://api.example.com/users")
.header("User-Agent", "hyper-client/1.0")
.header("Accept", "application/json")
.body(Empty::<bytes::Bytes>::new())?;
let response = client.request(request).await?;
if response.status().is_success() {
let body = hyper::body::to_bytes(response.into_body()).await?;
println!("Success: {}", String::from_utf8_lossy(&body));
} else {
println!("Error: {}", response.status());
}
Ok(())
}
POST・PUT・DELETEリクエストとJSONハンドリング
use hyper::{Client, Request, Body, Method};
use http_body_util::Full;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: Option<u32>,
name: String,
email: String,
age: u32,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::new();
// POSTリクエスト(JSON送信)
let new_user = User {
id: None,
name: "田中太郎".to_string(),
email: "[email protected]".to_string(),
age: 30,
};
let json_body = serde_json::to_string(&new_user)?;
let request = Request::builder()
.method(Method::POST)
.uri("https://api.example.com/users")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-token")
.body(Full::new(Bytes::from(json_body)))?;
let response = client.request(request).await?;
if response.status().is_success() {
let body = hyper::body::to_bytes(response.into_body()).await?;
let created_user: User = serde_json::from_slice(&body)?;
println!("ユーザー作成完了: {:?}", created_user);
} else {
println!("エラー: {}", response.status());
}
// PUTリクエスト(データ更新)
let updated_user = User {
id: Some(1),
name: "田中次郎".to_string(),
email: "[email protected]".to_string(),
age: 31,
};
let json_body = serde_json::to_string(&updated_user)?;
let put_request = Request::builder()
.method(Method::PUT)
.uri("https://api.example.com/users/1")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-token")
.body(Full::new(Bytes::from(json_body)))?;
let put_response = client.request(put_request).await?;
println!("更新ステータス: {}", put_response.status());
// DELETEリクエスト
let delete_request = Request::builder()
.method(Method::DELETE)
.uri("https://api.example.com/users/1")
.header("Authorization", "Bearer your-token")
.body(Empty::<Bytes>::new())?;
let delete_response = client.request(delete_request).await?;
if delete_response.status().is_success() {
println!("ユーザー削除完了");
}
Ok(())
}
高度な設定(タイムアウト、認証、カスタムヘッダー)
use hyper::{Client, Request, Method, Uri};
use hyper::client::HttpConnector;
use hyper_tls::HttpsConnector;
use http_body_util::Empty;
use bytes::Bytes;
use std::time::Duration;
use tower::{ServiceBuilder, timeout::TimeoutLayer};
use tower_http::classify::StatusInRangeAsFailures;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// HTTPS対応クライアントの作成
let https = HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);
// カスタムヘッダーとタイムアウトの設定
let request = Request::builder()
.method(Method::GET)
.uri("https://secure-api.example.com/data")
.header("User-Agent", "MyApp/1.0 (Rust Hyper)")
.header("Accept", "application/json")
.header("Accept-Language", "ja-JP,en-US")
.header("X-API-Version", "v2")
.header("X-Request-ID", "req-12345")
.header("Authorization", "Bearer your-jwt-token")
.body(Empty::<Bytes>::new())?;
// タイムアウト付きリクエスト実行
let response = tokio::time::timeout(
Duration::from_secs(30),
client.request(request)
).await??;
println!("ステータス: {}", response.status());
println!("ヘッダー: {:#?}", response.headers());
// Basic認証の例
let basic_auth_request = Request::builder()
.method(Method::GET)
.uri("https://api.example.com/private")
.header("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=") // base64(username:password)
.body(Empty::<Bytes>::new())?;
let auth_response = client.request(basic_auth_request).await?;
println!("認証ステータス: {}", auth_response.status());
Ok(())
}
// カスタムコネクタとプロキシ設定
use hyper::client::connect::dns::GaiResolver;
use hyper::client::connect::HttpConnector;
async fn custom_connector_example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// カスタムDNSリゾルバーの設定
let mut connector = HttpConnector::new_with_resolver(GaiResolver::new());
connector.set_connect_timeout(Some(Duration::from_secs(5)));
connector.set_happy_eyeballs_timeout(Some(Duration::from_millis(300)));
connector.enforce_http(false); // HTTPSを許可
let client = Client::builder()
.http2_only(false) // HTTP/1.1も許可
.build(connector);
let response = client.get("http://example.com".parse()?).await?;
println!("カスタムコネクタ レスポンス: {}", response.status());
Ok(())
}
エラーハンドリングとリトライ機能
use hyper::{Client, StatusCode};
use http_body_util::Empty;
use bytes::Bytes;
use std::time::Duration;
use tokio::time::{sleep, timeout};
#[derive(Debug)]
enum HttpError {
RequestFailed(hyper::Error),
Timeout,
HttpStatus(StatusCode),
SerializationError(serde_json::Error),
}
impl From<hyper::Error> for HttpError {
fn from(err: hyper::Error) -> Self {
HttpError::RequestFailed(err)
}
}
impl From<serde_json::Error> for HttpError {
fn from(err: serde_json::Error) -> Self {
HttpError::SerializationError(err)
}
}
// リトライ機能付きHTTPクライアント
struct RetryableClient {
client: Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>,
max_retries: usize,
base_delay: Duration,
}
impl RetryableClient {
fn new() -> Self {
let https = hyper_tls::HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);
Self {
client,
max_retries: 3,
base_delay: Duration::from_millis(500),
}
}
async fn get_with_retry(&self, uri: hyper::Uri) -> Result<String, HttpError> {
let mut last_error = None;
for attempt in 0..=self.max_retries {
match self.attempt_request(uri.clone()).await {
Ok(response) => return Ok(response),
Err(e) => {
last_error = Some(e);
if attempt < self.max_retries {
let delay = self.base_delay * 2_u32.pow(attempt as u32);
println!("試行 {} 失敗. {}ms後に再試行...", attempt + 1, delay.as_millis());
sleep(delay).await;
}
}
}
}
Err(last_error.unwrap())
}
async fn attempt_request(&self, uri: hyper::Uri) -> Result<String, HttpError> {
let request = hyper::Request::builder()
.method(hyper::Method::GET)
.uri(uri)
.header("User-Agent", "RetryableClient/1.0")
.body(Empty::<Bytes>::new())
.map_err(|e| HttpError::RequestFailed(hyper::Error::from(e)))?;
// タイムアウト付きリクエスト
let response = timeout(
Duration::from_secs(10),
self.client.request(request)
).await.map_err(|_| HttpError::Timeout)??;
// ステータスコードチェック
let status = response.status();
if !status.is_success() {
return Err(HttpError::HttpStatus(status));
}
// レスポンス読み取り
let body_bytes = hyper::body::to_bytes(response.into_body()).await?;
let body_str = String::from_utf8(body_bytes.to_vec())
.map_err(|_| HttpError::RequestFailed(
hyper::Error::from(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid UTF-8"
))
))?;
Ok(body_str)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = RetryableClient::new();
match client.get_with_retry("https://httpbin.org/get".parse()?).await {
Ok(response) => println!("成功: {}", response),
Err(HttpError::RequestFailed(e)) => println!("リクエストエラー: {}", e),
Err(HttpError::Timeout) => println!("タイムアウトエラー"),
Err(HttpError::HttpStatus(status)) => println!("HTTPエラー: {}", status),
Err(HttpError::SerializationError(e)) => println!("シリアライゼーションエラー: {}", e),
}
Ok(())
}
並行処理とストリーミング
use hyper::{Client, Uri};
use http_body_util::Empty;
use bytes::Bytes;
use futures::future::join_all;
use tokio::task::JoinHandle;
use std::time::Instant;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::new();
// 複数URLの並列取得
let urls = vec![
"http://httpbin.org/delay/1",
"http://httpbin.org/delay/2",
"http://httpbin.org/delay/1",
"http://httpbin.org/status/200",
];
let start = Instant::now();
// 並列リクエストの実行
let handles: Vec<JoinHandle<Result<String, Box<dyn std::error::Error + Send + Sync>>>> = urls
.into_iter()
.map(|url| {
let client = client.clone();
tokio::spawn(async move {
let uri: Uri = url.parse()?;
let response = client.get(uri).await?;
let body = hyper::body::to_bytes(response.into_body()).await?;
Ok(format!("URL: {} - Status: {}", url, response.status()))
})
})
.collect();
// 全てのリクエストの完了を待機
let results = join_all(handles).await;
println!("並列リクエスト完了時間: {:?}", start.elapsed());
for (i, result) in results.into_iter().enumerate() {
match result {
Ok(Ok(response)) => println!("リクエスト {}: {}", i + 1, response),
Ok(Err(e)) => println!("リクエスト {} エラー: {}", i + 1, e),
Err(e) => println!("タスク {} エラー: {}", i + 1, e),
}
}
Ok(())
}
// ストリーミング処理の例
use hyper::body::{Body, Frame};
use futures::stream::StreamExt;
async fn streaming_example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::new();
let response = client.get("http://httpbin.org/stream/10".parse()?).await?;
if response.status().is_success() {
let mut body = response.into_body();
let mut total_bytes = 0;
// ストリーミングでデータを読み取り
while let Some(frame) = body.frame().await {
match frame? {
Frame::Data(chunk) => {
total_bytes += chunk.len();
println!("受信チャンク: {} bytes (累計: {} bytes)", chunk.len(), total_bytes);
// チャンクデータの処理
let chunk_str = String::from_utf8_lossy(&chunk);
println!("データ: {}", chunk_str.trim());
}
Frame::Trailers(_trailers) => {
println!("トレーラー受信");
}
}
}
println!("ストリーミング完了: 総受信 {} bytes", total_bytes);
}
Ok(())
}
プロダクション向け設定と監視
use hyper::{Client, Request, Method};
use hyper_tls::HttpsConnector;
use http_body_util::Full;
use bytes::Bytes;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use tokio::time::timeout;
// メトリクス収集
#[derive(Debug, Default)]
struct HttpMetrics {
total_requests: AtomicU64,
successful_requests: AtomicU64,
failed_requests: AtomicU64,
total_duration: AtomicU64, // ミリ秒
}
impl HttpMetrics {
fn record_request(&self, success: bool, duration: Duration) {
self.total_requests.fetch_add(1, Ordering::Relaxed);
self.total_duration.fetch_add(duration.as_millis() as u64, Ordering::Relaxed);
if success {
self.successful_requests.fetch_add(1, Ordering::Relaxed);
} else {
self.failed_requests.fetch_add(1, Ordering::Relaxed);
}
}
fn print_stats(&self) {
let total = self.total_requests.load(Ordering::Relaxed);
let success = self.successful_requests.load(Ordering::Relaxed);
let failed = self.failed_requests.load(Ordering::Relaxed);
let total_duration = self.total_duration.load(Ordering::Relaxed);
if total > 0 {
let avg_duration = total_duration as f64 / total as f64;
let success_rate = (success as f64 / total as f64) * 100.0;
println!("HTTP メトリクス:");
println!(" 総リクエスト数: {}", total);
println!(" 成功: {} ({:.1}%)", success, success_rate);
println!(" 失敗: {}", failed);
println!(" 平均応答時間: {:.1}ms", avg_duration);
}
}
}
// プロダクション用HTTPクライアント
struct ProductionHttpClient {
client: Client<HttpsConnector<hyper::client::HttpConnector>>,
metrics: Arc<HttpMetrics>,
timeout: Duration,
}
impl ProductionHttpClient {
fn new() -> Self {
// TLS設定とコネクション設定
let https = HttpsConnector::new();
let client = Client::builder()
.pool_idle_timeout(Duration::from_secs(60))
.pool_max_idle_per_host(10)
.http2_only(false)
.build(https);
Self {
client,
metrics: Arc::new(HttpMetrics::default()),
timeout: Duration::from_secs(30),
}
}
async fn post_json<T: serde::Serialize>(
&self,
url: &str,
payload: &T,
auth_token: &str,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let start = Instant::now();
let mut success = false;
let result = async {
let json_body = serde_json::to_string(payload)?;
let request = Request::builder()
.method(Method::POST)
.uri(url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", format!("Bearer {}", auth_token))
.header("User-Agent", "ProductionClient/1.0")
.header("X-Request-ID", uuid::Uuid::new_v4().to_string())
.body(Full::new(Bytes::from(json_body)))?;
let response = timeout(self.timeout, self.client.request(request)).await??;
if response.status().is_success() {
success = true;
let body = hyper::body::to_bytes(response.into_body()).await?;
Ok(String::from_utf8(body.to_vec())?)
} else {
Err(format!("HTTP Error: {}", response.status()).into())
}
}.await;
// メトリクス記録
self.metrics.record_request(success, start.elapsed());
result
}
fn get_metrics(&self) -> Arc<HttpMetrics> {
self.metrics.clone()
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = ProductionHttpClient::new();
// サンプルペイロード
use serde_json::json;
let payload = json!({
"name": "田中太郎",
"email": "[email protected]",
"department": "engineering"
});
// APIコール実行
match client.post_json(
"https://api.example.com/users",
&payload,
"your-auth-token"
).await {
Ok(response) => println!("API成功: {}", response),
Err(e) => println!("APIエラー: {}", e),
}
// メトリクス表示
client.get_metrics().print_stats();
Ok(())
}
// ヘルスチェック機能
impl ProductionHttpClient {
async fn health_check(&self, endpoints: Vec<&str>) -> Vec<(String, bool, Duration)> {
let handles = endpoints.into_iter().map(|endpoint| {
let client = self.client.clone();
let endpoint = endpoint.to_string();
tokio::spawn(async move {
let start = Instant::now();
let uri: hyper::Uri = endpoint.parse().unwrap();
let success = match timeout(Duration::from_secs(5), client.get(uri)).await {
Ok(Ok(response)) => response.status().is_success(),
_ => false,
};
(endpoint, success, start.elapsed())
})
});
let results = futures::future::join_all(handles).await;
results.into_iter().map(|r| r.unwrap()).collect()
}
}