ureq

Rust向けの軽量で依存関係最小のHTTPクライアント。同期のみの設計により、シンプルで理解しやすいAPI。コンパイル時間を重視し、async不要の用途に最適化。JSON処理、基本認証、Cookie管理等の基本機能を効率的に提供。

HTTPクライアントRust軽量シンプル同期処理

GitHub概要

algesten/ureq

A simple, safe HTTP client

スター1,992
ウォッチ9
フォーク205
作成日:2018年6月10日
言語:Rust
ライセンス:Apache License 2.0

トピックス

httphttpclientrustrust-library

スター履歴

algesten/ureq Star History
データ取得日時: 2025/10/22 04:10

ライブラリ

ureq

概要

ureqは「シンプルで安全なHTTPクライアント」として開発されたRust用の軽量HTTPクライアントライブラリです。「HTTP APIとの連携に優れた、Pure Rustによる安全性と理解しやすさを重視」したライブラリで、非同期I/Oではなくブロッキング I/Oを使用することでAPIをシンプルに保ち、依存関係を最小限に抑制。JSON、Cookie、プロキシ、HTTPS、文字セットデコーディングなどの基本機能を提供し、コンパイル時間への影響を最小限に抑えたい開発者に最適なHTTPクライアントです。

詳細

ureq 2025年版は「軽量さとシンプルさ」を最優先に設計されたRust HTTPクライアントとして確固たる地位を確立しています。Pure Rustによる実装で unsafeコードを直接使用せず、ゼロコスト抽象化を活用してパフォーマンスと安全性を両立。同期処理ベースの設計により、async/awaitの複雑さを回避しつつ、直感的で分かりやすいAPIを提供します。クレート機能フラグによる柔軟な機能選択が可能で、必要最小限の機能のみを有効化してバイナリサイズとコンパイル時間を最適化できます。

主な特徴

  • Pure Rust実装: unsafe使用回避による安全性と理解しやすさ
  • 軽量設計: 最小限の依存関係とコンパイル時間の短縮
  • シンプルAPI: 直感的で分かりやすいブロッキングI/Oベース
  • Agent機能: 接続プールとCookieストアによる効率的な通信
  • TLS柔軟性: rustls、native-tls両対応とプロバイダー選択
  • 豊富な機能フラグ: JSON、圧縮、プロキシ等の選択的有効化

メリット・デメリット

メリット

  • 軽量でコンパイル時間が短く開発効率が向上
  • Pure Rust実装による高い安全性とメモリ安全性
  • シンプルで直感的なAPIによる学習コストの低さ
  • 豊富な機能フラグによる必要機能のみの選択が可能
  • async/awaitの複雑さを回避したストレートな処理
  • 企業環境でのプロキシやTLS設定への柔軟な対応

デメリット

  • 同期処理のみで非同期処理に未対応
  • 高負荷な並列リクエストには不向き
  • tokioエコシステムとの統合が困難
  • 大規模な並列処理が必要な場面では性能制約
  • レスポンシブなGUIアプリケーションには不適切
  • 非同期ライブラリと比較してスループットが劣る場合

参考ページ

書き方の例

インストールと基本セットアップ

# Cargo.toml - 基本設定
[dependencies]
ureq = "3.0"

# 機能フラグ付きインストール
[dependencies]
ureq = { version = "3.0", features = ["json", "cookies", "gzip"] }

# TLSプロバイダー選択
[dependencies]
ureq = { version = "3.0", features = ["native-tls"] }

# SOCKS proxy support
[dependencies]
ureq = { version = "3.0", features = ["socks-proxy"] }

# 最小構成(JSON、Cookies無効)
[dependencies]
ureq = { version = "3.0", default-features = false }
// 基本的なインポート
use ureq;
use std::collections::HashMap;

// エラーハンドリング用
use ureq::{Error, Response};

// JSON機能が有効な場合
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};

fn main() {
    println!("ureq HTTPクライアント開始");
}

基本的なリクエスト(GET/POST/PUT/DELETE)

use ureq;
use std::io::Read;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 基本的なGETリクエスト
    let response = ureq::get("https://api.example.com/users")
        .call()?;
    
    println!("ステータス: {}", response.status());
    println!("ヘッダー: {:?}", response.headers_names());
    
    let body: String = response.into_string()?;
    println!("レスポンス: {}", body);

    // ヘッダー付きGETリクエスト
    let response = ureq::get("https://api.example.com/data")
        .header("User-Agent", "MyApp/1.0")
        .header("Accept", "application/json")
        .header("Authorization", "Bearer your-token")
        .call()?;

    // レスポンスヘッダーの確認
    if let Some(content_type) = response.header("content-type") {
        println!("Content-Type: {}", content_type);
    }

    // パラメータ付きGETリクエスト(クエリ文字列)
    let response = ureq::get("https://api.example.com/users")
        .query("page", "1")
        .query("limit", "10")
        .query("sort", "created_at")
        .call()?;

    println!("リクエストURL: {}", response.get_url());

    // POSTリクエスト(JSON送信) - JSON機能が有効な場合
    #[cfg(feature = "json")]
    {
        use serde_json::json;
        
        let user_data = json!({
            "name": "田中太郎",
            "email": "[email protected]",
            "age": 30
        });

        let response = ureq::post("https://api.example.com/users")
            .header("Content-Type", "application/json")
            .header("Authorization", "Bearer your-token")
            .send_json(&user_data)?;

        if response.status() == 201 {
            let created_user: serde_json::Value = response.into_json()?;
            println!("ユーザー作成完了: ID={}", created_user["id"]);
        }
    }

    // POSTリクエスト(フォームデータ送信)
    let form_data = [
        ("username", "testuser"),
        ("password", "secret123"),
    ];

    let response = ureq::post("https://api.example.com/login")
        .send_form(&form_data)?;

    // POSTリクエスト(文字列送信)
    let response = ureq::post("https://api.example.com/webhook")
        .header("Content-Type", "text/plain")
        .send_string("Hello, webhook!")?;

    // PUTリクエスト(データ更新)
    #[cfg(feature = "json")]
    {
        let updated_data = json!({
            "name": "田中次郎",
            "email": "[email protected]"
        });

        let response = ureq::put("https://api.example.com/users/123")
            .header("Authorization", "Bearer your-token")
            .send_json(&updated_data)?;

        println!("更新ステータス: {}", response.status());
    }

    // DELETEリクエスト
    let response = ureq::delete("https://api.example.com/users/123")
        .header("Authorization", "Bearer your-token")
        .call()?;

    if response.status() == 204 {
        println!("削除完了");
    }

    // HEADリクエスト(ヘッダー情報のみ)
    let response = ureq::head("https://api.example.com/users/123")
        .call()?;

    if let Some(content_length) = response.header("content-length") {
        println!("Content-Length: {}", content_length);
    }

    Ok(())
}

高度な設定とエラーハンドリング(Agent、タイムアウト、プロキシ等)

use ureq::{Agent, AgentBuilder, Error};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Agentの作成(接続プールとCookieストア有効)
    let agent = AgentBuilder::new()
        .timeout_read(Duration::from_secs(30))
        .timeout_write(Duration::from_secs(30))
        .timeout_connect(Duration::from_secs(10))
        .user_agent("MyApp/1.0 (ureq)")
        .build();

    // Agentでのリクエスト実行
    let response = agent
        .get("https://api.example.com/data")
        .header("Accept", "application/json")
        .call()?;

    // Cookie機能が有効な場合の利用
    #[cfg(feature = "cookies")]
    {
        // Cookieは自動的に保存・送信される
        let _login_response = agent
            .post("https://api.example.com/login")
            .send_form(&[("user", "testuser"), ("pass", "secret")])?;

        // 以降のリクエストでCookieが自動送信される
        let protected_response = agent
            .get("https://api.example.com/protected")
            .call()?;
    }

    // プロキシ設定
    #[cfg(feature = "socks-proxy")]
    {
        let proxy_agent = AgentBuilder::new()
            .proxy("socks5://127.0.0.1:1080")
            .build();

        let response = proxy_agent
            .get("https://api.example.com/via-proxy")
            .call()?;
    }

    // タイムアウト設定のカスタマイズ
    let timeout_agent = AgentBuilder::new()
        .timeout(Duration::from_secs(60))  // 全体タイムアウト
        .timeout_connect(Duration::from_secs(5))  // 接続タイムアウト
        .timeout_read(Duration::from_secs(30))    // 読み込みタイムアウト
        .timeout_write(Duration::from_secs(30))   // 書き込みタイムアウト
        .build();

    // TLS設定のカスタマイズ
    use ureq::tls::{TlsConfig, RootCerts};

    let tls_agent = AgentBuilder::new()
        .tls_config(
            TlsConfig::builder()
                .root_certs(RootCerts::PlatformVerifier)
                .build()
        )
        .build();

    // エラーハンドリングの詳細
    match ureq::get("https://api.example.com/might-fail").call() {
        Ok(response) => {
            println!("成功: ステータス {}", response.status());
            
            // レスポンスの種類による処理
            match response.status() {
                200 => {
                    let body = response.into_string()?;
                    println!("データ: {}", body);
                },
                404 => println!("リソースが見つかりません"),
                _ => println!("その他のステータス: {}", response.status()),
            }
        },
        Err(Error::Status(code, response)) => {
            println!("HTTPエラー: {} - {}", code, response.status_text());
            
            // エラーレスポンスの内容確認
            if let Ok(error_body) = response.into_string() {
                println!("エラー詳細: {}", error_body);
            }
        },
        Err(Error::Transport(transport_error)) => {
            println!("トランスポートエラー: {}", transport_error);
        },
        Err(e) => {
            println!("その他のエラー: {}", e);
        }
    }

    Ok(())
}

// リトライ機能の実装
fn request_with_retry(
    url: &str,
    max_retries: usize,
) -> Result<ureq::Response, ureq::Error> {
    let agent = ureq::agent();
    
    for attempt in 0..=max_retries {
        match agent.get(url).call() {
            Ok(response) => return Ok(response),
            Err(Error::Status(code, _)) if code >= 500 && attempt < max_retries => {
                println!("サーバーエラー {}, 再試行中... ({}/{})", code, attempt + 1, max_retries);
                std::thread::sleep(Duration::from_secs(2_u64.pow(attempt as u32)));
                continue;
            },
            Err(Error::Transport(_)) if attempt < max_retries => {
                println!("通信エラー, 再試行中... ({}/{})", attempt + 1, max_retries);
                std::thread::sleep(Duration::from_secs(2_u64.pow(attempt as u32)));
                continue;
            },
            Err(e) => return Err(e),
        }
    }
    
    unreachable!()
}

// 認証情報付きリクエスト
fn authenticated_request() -> Result<(), Box<dyn std::error::Error>> {
    let agent = ureq::agent();
    
    // Bearer認証
    let response = agent
        .get("https://api.example.com/protected")
        .header("Authorization", "Bearer your-jwt-token")
        .call()?;

    // Basic認証
    let response = agent
        .get("https://api.example.com/basic-auth")
        .auth("username", Some("password"))
        .call()?;

    Ok(())
}

JSON処理とデータシリアライゼーション

use ureq;
use serde::{Deserialize, Serialize};
use serde_json;

#[derive(Serialize, Deserialize, Debug)]
struct User {
    id: Option<u32>,
    name: String,
    email: String,
    age: u32,
}

#[derive(Deserialize, Debug)]
struct ApiResponse<T> {
    success: bool,
    data: T,
    message: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = ureq::agent();

    // JSON レスポンスの自動デシリアライゼーション
    let users_response = agent
        .get("https://api.example.com/users")
        .header("Accept", "application/json")
        .call()?;

    #[cfg(feature = "json")]
    {
        // 直接構造体にデシリアライズ
        let users: Vec<User> = users_response.into_json()?;
        
        for user in &users {
            println!("ユーザー: {} ({})", user.name, user.email);
        }

        // ネストした構造体のデシリアライゼーション
        let api_response: ApiResponse<Vec<User>> = agent
            .get("https://api.example.com/users-wrapped")
            .call()?
            .into_json()?;

        if api_response.success {
            println!("API成功: {}", api_response.message);
            for user in api_response.data {
                println!("  - {}: {}", user.name, user.email);
            }
        }

        // 新しいユーザーの作成
        let new_user = User {
            id: None,
            name: "新規ユーザー".to_string(),
            email: "[email protected]".to_string(),
            age: 25,
        };

        let created_user: User = agent
            .post("https://api.example.com/users")
            .header("Content-Type", "application/json")
            .send_json(&new_user)?
            .into_json()?;

        println!("作成されたユーザー: {:?}", created_user);

        // 部分的なJSONの処理
        let partial_update = serde_json::json!({
            "age": 26
        });

        let updated_user: User = agent
            .patch(&format!("https://api.example.com/users/{}", created_user.id.unwrap()))
            .send_json(&partial_update)?
            .into_json()?;

        println!("更新されたユーザー: {:?}", updated_user);
    }

    // 手動JSONパース(feature = "json"無しでも利用可能)
    let response = agent
        .get("https://api.example.com/data")
        .call()?;

    let json_string = response.into_string()?;
    let parsed_data: serde_json::Value = serde_json::from_str(&json_string)?;
    
    if let Some(name) = parsed_data["name"].as_str() {
        println!("名前: {}", name);
    }

    Ok(())
}

// エラーレスポンスの処理
#[derive(Deserialize, Debug)]
struct ErrorResponse {
    error: String,
    code: u32,
    details: Option<String>,
}

fn handle_api_errors() -> Result<(), Box<dyn std::error::Error>> {
    let agent = ureq::agent();

    match agent.get("https://api.example.com/might-error").call() {
        Ok(response) => {
            #[cfg(feature = "json")]
            {
                let data: serde_json::Value = response.into_json()?;
                println!("成功: {:?}", data);
            }
        },
        Err(ureq::Error::Status(code, response)) => {
            println!("HTTPエラー: {}", code);
            
            #[cfg(feature = "json")]
            {
                if let Ok(error_detail) = response.into_json::<ErrorResponse>() {
                    println!("エラー詳細: {:?}", error_detail);
                } else {
                    println!("エラーレスポンスのパースに失敗");
                }
            }
        },
        Err(e) => {
            println!("リクエストエラー: {}", e);
        }
    }

    Ok(())
}

ファイル操作とストリーミング処理

use ureq;
use std::fs::File;
use std::io::{Write, Read, copy};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = ureq::agent();

    // ファイルダウンロード
    let response = agent
        .get("https://api.example.com/files/document.pdf")
        .call()?;

    let mut file = File::create("downloaded_document.pdf")?;
    let mut reader = response.into_reader();
    copy(&mut reader, &mut file)?;
    
    println!("ファイルダウンロード完了");

    // 大容量ファイルのストリーミングダウンロード
    let response = agent
        .get("https://api.example.com/files/large-file.zip")
        .call()?;

    let mut file = File::create("large_file.zip")?;
    let mut reader = response.into_reader();
    let mut buffer = [0; 8192];
    let mut total_downloaded = 0;

    loop {
        let bytes_read = reader.read(&mut buffer)?;
        if bytes_read == 0 {
            break;
        }
        
        file.write_all(&buffer[..bytes_read])?;
        total_downloaded += bytes_read;
        
        // 進捗表示
        if total_downloaded % (1024 * 1024) == 0 {
            println!("ダウンロード済み: {} MB", total_downloaded / (1024 * 1024));
        }
    }

    println!("大容量ファイルダウンロード完了: {} バイト", total_downloaded);

    // ファイルアップロード
    let file_content = std::fs::read("upload_file.txt")?;
    
    let response = agent
        .post("https://api.example.com/upload")
        .header("Content-Type", "text/plain")
        .send_bytes(&file_content)?;

    println!("ファイルアップロード: {}", response.status());

    // マルチパートフォームデータでのファイルアップロード
    // 注意: ureqはマルチパートを直接サポートしていないため、
    // multipart crateなどを併用する必要があります
    
    Ok(())
}

// レスポンスサイズ制限付きダウンロード
fn limited_download(url: &str, max_size: usize) -> Result<String, Box<dyn std::error::Error>> {
    let agent = ureq::agent();
    let response = agent.get(url).call()?;
    
    let mut reader = response.into_reader();
    let mut buffer = Vec::new();
    let mut temp_buffer = [0; 1024];
    
    loop {
        let bytes_read = reader.read(&mut temp_buffer)?;
        if bytes_read == 0 {
            break;
        }
        
        if buffer.len() + bytes_read > max_size {
            return Err("ファイルサイズが制限を超えています".into());
        }
        
        buffer.extend_from_slice(&temp_buffer[..bytes_read]);
    }
    
    Ok(String::from_utf8(buffer)?)
}

// ストリーミングJSON処理(行区切りJSON)
fn process_streaming_json(url: &str) -> Result<(), Box<dyn std::error::Error>> {
    let agent = ureq::agent();
    let response = agent.get(url).call()?;
    
    let reader = response.into_reader();
    let mut lines = std::io::BufRead::lines(std::io::BufReader::new(reader));
    
    for line in lines {
        let line = line?;
        if !line.trim().is_empty() {
            match serde_json::from_str::<serde_json::Value>(&line) {
                Ok(json_data) => {
                    println!("受信: {}", json_data);
                },
                Err(e) => {
                    println!("JSONパースエラー: {} - Line: {}", e, line);
                }
            }
        }
    }
    
    Ok(())
}

実用的な活用例とベストプラクティス

use ureq::{Agent, AgentBuilder};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use std::sync::Arc;

// REST API クライアント構造体
#[derive(Clone)]
pub struct ApiClient {
    agent: Agent,
    base_url: String,
    api_key: Option<String>,
}

impl ApiClient {
    pub fn new(base_url: &str) -> Self {
        let agent = AgentBuilder::new()
            .timeout(Duration::from_secs(30))
            .user_agent("ApiClient/1.0")
            .build();

        Self {
            agent,
            base_url: base_url.to_string(),
            api_key: None,
        }
    }

    pub fn with_api_key(mut self, api_key: &str) -> Self {
        self.api_key = Some(api_key.to_string());
        self
    }

    fn build_url(&self, path: &str) -> String {
        format!("{}/{}", self.base_url.trim_end_matches('/'), path.trim_start_matches('/'))
    }

    fn prepare_request(&self, request: ureq::Request) -> ureq::Request {
        let mut req = request.header("Accept", "application/json");
        
        if let Some(ref api_key) = self.api_key {
            req = req.header("Authorization", &format!("Bearer {}", api_key));
        }
        
        req
    }

    pub fn get(&self, path: &str) -> Result<ureq::Response, ureq::Error> {
        let url = self.build_url(path);
        let request = self.agent.get(&url);
        self.prepare_request(request).call()
    }

    #[cfg(feature = "json")]
    pub fn post_json<T: Serialize>(&self, path: &str, data: &T) -> Result<ureq::Response, ureq::Error> {
        let url = self.build_url(path);
        let request = self.agent.post(&url);
        self.prepare_request(request)
            .header("Content-Type", "application/json")
            .send_json(data)
    }

    #[cfg(feature = "json")]
    pub fn put_json<T: Serialize>(&self, path: &str, data: &T) -> Result<ureq::Response, ureq::Error> {
        let url = self.build_url(path);
        let request = self.agent.put(&url);
        self.prepare_request(request)
            .header("Content-Type", "application/json")
            .send_json(data)
    }

    pub fn delete(&self, path: &str) -> Result<ureq::Response, ureq::Error> {
        let url = self.build_url(path);
        let request = self.agent.delete(&url);
        self.prepare_request(request).call()
    }
}

// 使用例
#[cfg(feature = "json")]
fn api_client_example() -> Result<(), Box<dyn std::error::Error>> {
    let client = ApiClient::new("https://api.example.com/v1")
        .with_api_key("your-api-key");

    // GET リクエスト
    let users_response = client.get("users")?;
    let users: Vec<User> = users_response.into_json()?;
    
    println!("ユーザー数: {}", users.len());

    // POST リクエスト
    let new_user = User {
        id: None,
        name: "新規ユーザー".to_string(),
        email: "[email protected]".to_string(),
        age: 30,
    };

    let created_response = client.post_json("users", &new_user)?;
    let created_user: User = created_response.into_json()?;
    
    println!("作成されたユーザー: {:?}", created_user);

    Ok(())
}

// 設定管理とベストプラクティス
pub struct HttpConfig {
    pub timeout: Duration,
    pub user_agent: String,
    pub max_redirects: usize,
}

impl Default for HttpConfig {
    fn default() -> Self {
        Self {
            timeout: Duration::from_secs(30),
            user_agent: "MyApp/1.0".to_string(),
            max_redirects: 5,
        }
    }
}

// グローバル設定を使用したHTTPクライアント
pub fn create_configured_agent(config: &HttpConfig) -> Agent {
    AgentBuilder::new()
        .timeout(config.timeout)
        .user_agent(&config.user_agent)
        .redirects(config.max_redirects)
        .build()
}

// エラーハンドリングのベストプラクティス
#[derive(Debug)]
pub enum ApiError {
    NetworkError(ureq::Error),
    ParseError(String),
    BusinessLogicError(String),
}

impl std::fmt::Display for ApiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApiError::NetworkError(e) => write!(f, "ネットワークエラー: {}", e),
            ApiError::ParseError(e) => write!(f, "パースエラー: {}", e),
            ApiError::BusinessLogicError(e) => write!(f, "ビジネスロジックエラー: {}", e),
        }
    }
}

impl std::error::Error for ApiError {}

impl From<ureq::Error> for ApiError {
    fn from(error: ureq::Error) -> Self {
        ApiError::NetworkError(error)
    }
}

// 堅牢なAPIコール関数
pub fn robust_api_call(url: &str) -> Result<serde_json::Value, ApiError> {
    let agent = ureq::agent();
    
    match agent.get(url).call() {
        Ok(response) => {
            #[cfg(feature = "json")]
            {
                response.into_json()
                    .map_err(|e| ApiError::ParseError(e.to_string()))
            }
            #[cfg(not(feature = "json"))]
            {
                Err(ApiError::ParseError("JSON機能が無効です".to_string()))
            }
        },
        Err(ureq::Error::Status(code, response)) => {
            match code {
                400..=499 => Err(ApiError::BusinessLogicError(
                    format!("クライアントエラー: {}", code)
                )),
                500..=599 => Err(ApiError::NetworkError(
                    ureq::Error::Status(code, response)
                )),
                _ => Err(ApiError::NetworkError(
                    ureq::Error::Status(code, response)
                )),
            }
        },
        Err(e) => Err(ApiError::NetworkError(e)),
    }
}

// メイン関数での統合使用例
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 基本的な使用
    let response = ureq::get("https://api.example.com/status").call()?;
    println!("サービス状態: {}", response.status());

    // 設定されたエージェントの使用
    let config = HttpConfig::default();
    let agent = create_configured_agent(&config);
    
    let response = agent
        .get("https://api.example.com/data")
        .header("Accept", "application/json")
        .call()?;

    // 堅牢なAPIコール
    match robust_api_call("https://api.example.com/users") {
        Ok(data) => println!("データ取得成功: {:?}", data),
        Err(e) => println!("エラー: {}", e),
    }

    #[cfg(feature = "json")]
    {
        // APIクライアントの使用
        api_client_example()?;
    }

    Ok(())
}