HTTP

Dart公式のHTTPクライアントパッケージ。シンプルで軽量な設計により、基本的なHTTPリクエスト・レスポンス処理を提供。JSON取得、サーバーレスポンス処理、ファイルダウンロードなどFlutterアプリのネットワーキングコードの基盤となる。

HTTPクライアントDartFlutter軽量非同期マルチプラットフォーム

GitHub概要

dart-lang/http

A composable API for making HTTP requests in Dart.

スター1,084
ウォッチ54
フォーク388
作成日:2014年12月17日
言語:Dart
ライセンス:BSD 3-Clause "New" or "Revised" License

トピックス

dartflutterhttphttp-client

スター履歴

dart-lang/http Star History
データ取得日時: 2025/10/22 09:55

ライブラリ

http

概要

httpは「Dart向けの軽量で汎用的なHTTPクライアントライブラリ」として開発された、Dartエコシステムで最も基本的で信頼性の高いHTTPクライアントライブラリです。「Dart公式推奨のHTTPソリューション」として位置づけられ、シンプルで直感的なFuture-based APIを通じて、RESTful API統合、データ取得、認証処理などのHTTP通信要件を包括的にサポート。Flutter、Web、デスクトップ、サーバーアプリケーション全般でマルチプラットフォーム対応を実現し、Dart開発者にとって事実上の標準HTTPライブラリとして確立されています。

詳細

http 2025年版はDart HTTP通信の基盤ライブラリとして圧倒的な地位を維持し続けています。Dart公式チームによる長期サポートにより安定した品質と後方互換性を保証し、Flutter、サーバーサイドDart、Webアプリケーション等あらゆるDartプラットフォームで一貫した開発体験を提供。軽量設計ながら本格的なHTTP機能を搭載し、プラットフォーム固有の最適化(BrowserClient、IOClient等)により各環境で最高のパフォーマンスを発揮します。組み込み可能なアーキテクチャによりRetryClient等の拡張ライブラリとシームレスに統合し、企業レベルのHTTP通信要件に対応可能です。

主な特徴

  • シンプルなFuture-based API: async/awaitパターンでの直感的な非同期HTTP処理
  • マルチプラットフォーム対応: Web(BrowserClient)、モバイル・デスクトップ(IOClient)の統一API
  • 組み込み可能設計: BaseClientを拡張したカスタムクライアント実装が容易
  • 軽量かつ高性能: 最小限の依存関係で高速な HTTP/1.1 通信を実現
  • 豊富な拡張機能: RetryClient、認証、ログ機能等の公式・サードパーティ拡張
  • Dart公式サポート: 公式チームによる継続的な開発とメンテナンス

メリット・デメリット

メリット

  • Dart公式推奨ライブラリとしての圧倒的な信頼性と安定性
  • Flutter、Web、サーバーサイド全プラットフォームでの統一された開発体験
  • 軽量でシンプルなAPIによる学習コストの低さと高い開発効率
  • 豊富なコミュニティサポートと拡張ライブラリエコシステム
  • Future-based設計によるDartの非同期プログラミングとの完全統合
  • プラットフォーム最適化による各環境での最高パフォーマンス発揮

デメリット

  • HTTP/1.1中心の設計でHTTP/2・HTTP/3機能は限定的
  • 高度な機能(接続プール管理、詳細キャッシュ制御)は別ライブラリが必要
  • 基本機能中心のため複雑なHTTP処理には追加実装が必要
  • プラットフォーム固有の最適化機能にアクセスするには専用クライアント使用
  • 同期処理API非対応(すべて非同期Future-based)
  • 大規模並列リクエストでのパフォーマンス制約

参考ページ

書き方の例

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

# httpパッケージの追加
dart pub add http

# pubspec.yamlでの依存関係確認
cat pubspec.yaml

# Flutter プロジェクトの場合
flutter pub add http

# Dart環境での確認
dart pub deps

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

import 'package:http/http.dart' as http;
import 'dart:convert';

// 基本的なGETリクエスト
void main() async {
  // シンプルなGETリクエスト
  final response = await http.get(Uri.parse('https://api.example.com/users'));
  
  if (response.statusCode == 200) {
    print('ステータスコード: ${response.statusCode}');
    print('レスポンスボディ: ${response.body}');
    
    // JSONとして解析
    final data = json.decode(response.body);
    print('パース済みデータ: $data');
  } else {
    print('リクエスト失敗: ${response.statusCode}');
  }
}

// クエリパラメータ付きGETリクエスト
Future<void> fetchUsersWithParams() async {
  final uri = Uri.https('api.example.com', '/users', {
    'page': '1',
    'limit': '10',
    'sort': 'created_at'
  });
  
  final response = await http.get(uri);
  print('リクエストURL: $uri');
  print('レスポンス: ${response.body}');
}

// POSTリクエスト(JSON送信)
Future<void> createUser() async {
  final userData = {
    'name': '田中太郎',
    'email': '[email protected]',
    'age': 30
  };

  final response = await http.post(
    Uri.parse('https://api.example.com/users'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-token'
    },
    body: json.encode(userData),
  );

  if (response.statusCode == 201) {
    final createdUser = json.decode(response.body);
    print('ユーザー作成成功: ${createdUser['id']}');
  } else {
    print('作成失敗: ${response.statusCode} - ${response.body}');
  }
}

// フォームデータPOSTリクエスト
Future<void> loginUser() async {
  final response = await http.post(
    Uri.parse('https://api.example.com/login'),
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body: {
      'username': 'testuser',
      'password': 'secret123'
    },
  );
  
  if (response.statusCode == 200) {
    print('ログイン成功');
  }
}

// PUTリクエスト(データ更新)
Future<void> updateUser(String userId) async {
  final updatedData = {
    'name': '田中次郎',
    'email': '[email protected]'
  };

  final response = await http.put(
    Uri.parse('https://api.example.com/users/$userId'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-token'
    },
    body: json.encode(updatedData),
  );

  print('更新結果: ${response.statusCode}');
}

// DELETEリクエスト
Future<void> deleteUser(String userId) async {
  final response = await http.delete(
    Uri.parse('https://api.example.com/users/$userId'),
    headers: {'Authorization': 'Bearer your-token'},
  );

  if (response.statusCode == 204) {
    print('ユーザー削除完了');
  }
}

カスタムヘッダーと認証処理

import 'package:http/http.dart' as http;
import 'dart:convert';

// カスタムヘッダーの設定
Future<void> requestWithCustomHeaders() async {
  final headers = {
    'User-Agent': 'MyDartApp/1.0',
    'Accept': 'application/json',
    'Accept-Language': 'ja-JP,en-US',
    'X-API-Version': 'v2',
    'X-Request-ID': 'req-12345'
  };

  final response = await http.get(
    Uri.parse('https://api.example.com/data'),
    headers: headers,
  );
  
  print('レスポンス: ${response.body}');
}

// Bearer Token認証
Future<void> authenticatedRequest(String token) async {
  final response = await http.get(
    Uri.parse('https://api.example.com/protected'),
    headers: {
      'Authorization': 'Bearer $token',
      'Content-Type': 'application/json'
    },
  );
  
  if (response.statusCode == 200) {
    print('認証成功: ${response.body}');
  } else if (response.statusCode == 401) {
    print('認証失敗: トークンを確認してください');
  }
}

// Basic認証
Future<void> basicAuthRequest(String username, String password) async {
  final credentials = base64Encode(utf8.encode('$username:$password'));
  
  final response = await http.get(
    Uri.parse('https://api.example.com/private'),
    headers: {
      'Authorization': 'Basic $credentials',
    },
  );
  
  print('Basic認証結果: ${response.statusCode}');
}

// API Key認証
Future<void> apiKeyRequest(String apiKey) async {
  final response = await http.get(
    Uri.parse('https://api.example.com/data'),
    headers: {
      'X-API-Key': apiKey,
      'Content-Type': 'application/json'
    },
  );
  
  print('API Key認証結果: ${response.statusCode}');
}

// 動的ヘッダー設定
Map<String, String> buildHeaders({String? token, bool includeUserAgent = true}) {
  final headers = <String, String>{
    'Content-Type': 'application/json',
  };
  
  if (token != null) {
    headers['Authorization'] = 'Bearer $token';
  }
  
  if (includeUserAgent) {
    headers['User-Agent'] = 'MyApp/1.0 (Dart)';
  }
  
  return headers;
}

エラーハンドリングとリトライ機能

import 'package:http/http.dart' as http;
import 'package:http/retry.dart';
import 'dart:convert';
import 'dart:io';

// 包括的なエラーハンドリング
Future<Map<String, dynamic>?> safeApiRequest(String url) async {
  try {
    final response = await http.get(Uri.parse(url));
    
    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else if (response.statusCode == 401) {
      print('認証エラー: トークンを確認してください');
    } else if (response.statusCode == 403) {
      print('権限エラー: アクセス権限がありません');
    } else if (response.statusCode == 404) {
      print('見つかりません: リソースが存在しません');
    } else if (response.statusCode == 429) {
      print('レート制限: しばらく待ってから再試行してください');
    } else if (response.statusCode >= 500) {
      print('サーバーエラー: ${response.statusCode}');
    }
    
  } on SocketException catch (e) {
    print('ネットワークエラー: $e');
  } on FormatException catch (e) {
    print('JSON解析エラー: $e');
  } catch (e) {
    print('予期しないエラー: $e');
  }
  
  return null;
}

// 手動リトライ実装
Future<http.Response?> requestWithRetry(
  String url, {
  int maxRetries = 3,
  Duration delay = const Duration(seconds: 1),
}) async {
  for (int attempt = 0; attempt < maxRetries; attempt++) {
    try {
      final response = await http.get(Uri.parse(url));
      
      // 成功時は即座に返す
      if (response.statusCode == 200) {
        return response;
      }
      
      // リトライ対象のステータスコード
      if (attempt < maxRetries - 1 && [429, 500, 502, 503, 504].contains(response.statusCode)) {
        print('試行 ${attempt + 1} 失敗. ${delay.inSeconds}秒後に再試行...');
        await Future.delayed(delay);
        delay = Duration(seconds: delay.inSeconds * 2); // バックオフ
        continue;
      }
      
      return response;
      
    } catch (e) {
      if (attempt == maxRetries - 1) {
        print('最大試行回数に達しました: $e');
        rethrow;
      }
      
      print('試行 ${attempt + 1} でエラー: $e. 再試行中...');
      await Future.delayed(delay);
    }
  }
  
  return null;
}

// RetryClientを使用した自動リトライ
Future<void> retryClientExample() async {
  final client = RetryClient(http.Client());
  
  try {
    final response = await client.get(
      Uri.parse('https://api.example.com/unstable-endpoint')
    );
    
    print('リトライ成功: ${response.statusCode}');
    print('レスポンス: ${response.body}');
    
  } catch (e) {
    print('最終的に失敗: $e');
  } finally {
    client.close();
  }
}

// カスタムリトライロジック
class CustomRetryClient extends http.BaseClient {
  final http.Client _inner;
  final int maxRetries;
  final Duration baseDelay;

  CustomRetryClient(this._inner, {this.maxRetries = 3, this.baseDelay = const Duration(seconds: 1)});

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    for (int attempt = 0; attempt < maxRetries; attempt++) {
      try {
        final response = await _inner.send(request);
        
        // 成功時またはリトライ不要時
        if (response.statusCode < 500 || attempt == maxRetries - 1) {
          return response;
        }
        
        // リトライ待機
        await Future.delayed(baseDelay * (attempt + 1));
        
      } catch (e) {
        if (attempt == maxRetries - 1) rethrow;
        await Future.delayed(baseDelay * (attempt + 1));
      }
    }
    
    throw Exception('最大リトライ回数を超過');
  }
}

Clientを使用した効率的なHTTP処理

import 'package:http/http.dart' as http;
import 'dart:convert';

// 基本的なClientの使用
Future<void> basicClientUsage() async {
  final client = http.Client();
  
  try {
    // 複数のリクエストで同じクライアントを再利用
    final response1 = await client.get(Uri.parse('https://api.example.com/users'));
    final response2 = await client.get(Uri.parse('https://api.example.com/posts'));
    final response3 = await client.post(
      Uri.parse('https://api.example.com/data'),
      body: json.encode({'key': 'value'}),
      headers: {'Content-Type': 'application/json'},
    );
    
    print('User request: ${response1.statusCode}');
    print('Posts request: ${response2.statusCode}');
    print('Post data: ${response3.statusCode}');
    
  } finally {
    // 必ずクライアントを閉じる
    client.close();
  }
}

// カスタムClientの実装(User-Agent自動追加)
class UserAgentClient extends http.BaseClient {
  final String userAgent;
  final http.Client _inner;

  UserAgentClient(this.userAgent, this._inner);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers['User-Agent'] = userAgent;
    return _inner.send(request);
  }
}

// ログ機能付きClient
class LoggingClient extends http.BaseClient {
  final http.Client _inner;

  LoggingClient(this._inner);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    print('🚀 ${request.method} ${request.url}');
    print('📤 Headers: ${request.headers}');
    
    final stopwatch = Stopwatch()..start();
    final response = await _inner.send(request);
    stopwatch.stop();
    
    print('📥 ${response.statusCode} (${stopwatch.elapsedMilliseconds}ms)');
    return response;
  }
}

// 認証Client
class AuthClient extends http.BaseClient {
  final String token;
  final http.Client _inner;

  AuthClient(this.token, this._inner);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers['Authorization'] = 'Bearer $token';
    return _inner.send(request);
  }
}

// 組み合わせたClientの使用例
Future<void> composedClientExample() async {
  final baseClient = http.Client();
  final userAgentClient = UserAgentClient('MyApp/1.0', baseClient);
  final loggingClient = LoggingClient(userAgentClient);
  final authClient = AuthClient('your-jwt-token', loggingClient);
  
  try {
    final response = await authClient.get(
      Uri.parse('https://api.example.com/protected-data')
    );
    
    print('最終レスポンス: ${response.body}');
    
  } finally {
    authClient.close();
  }
}

// APIクライアントクラス
class ApiClient {
  final http.Client _client;
  final String baseUrl;
  final Map<String, String> defaultHeaders;

  ApiClient({
    required this.baseUrl,
    this.defaultHeaders = const {},
    http.Client? client,
  }) : _client = client ?? http.Client();

  Future<Map<String, dynamic>> get(String endpoint, {Map<String, String>? headers}) async {
    final response = await _client.get(
      Uri.parse('$baseUrl$endpoint'),
      headers: {...defaultHeaders, ...?headers},
    );
    
    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('GET $endpoint failed: ${response.statusCode}');
    }
  }

  Future<Map<String, dynamic>> post(String endpoint, {required Map<String, dynamic> data, Map<String, String>? headers}) async {
    final response = await _client.post(
      Uri.parse('$baseUrl$endpoint'),
      headers: {
        'Content-Type': 'application/json',
        ...defaultHeaders,
        ...?headers
      },
      body: json.encode(data),
    );
    
    if (response.statusCode == 200 || response.statusCode == 201) {
      return json.decode(response.body);
    } else {
      throw Exception('POST $endpoint failed: ${response.statusCode}');
    }
  }

  void close() => _client.close();
}

// 使用例
Future<void> apiClientExample() async {
  final api = ApiClient(
    baseUrl: 'https://api.example.com',
    defaultHeaders: {'Authorization': 'Bearer your-token'},
  );

  try {
    final users = await api.get('/users');
    print('ユーザー一覧: $users');

    final newUser = await api.post('/users', data: {
      'name': '新しいユーザー',
      'email': '[email protected]'
    });
    print('作成されたユーザー: $newUser');

  } finally {
    api.close();
  }
}

並行処理とファイル操作

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';

// 複数URLの並行取得
Future<void> parallelRequests() async {
  final urls = [
    'https://api.example.com/users',
    'https://api.example.com/posts',
    'https://api.example.com/comments',
    'https://api.example.com/categories'
  ];

  final futures = urls.map((url) => http.get(Uri.parse(url)));
  final responses = await Future.wait(futures);

  for (int i = 0; i < responses.length; i++) {
    print('${urls[i]}: ${responses[i].statusCode}');
  }
}

// 順次実行でのページネーション取得
Future<List<Map<String, dynamic>>> fetchAllPages(String baseUrl) async {
  final allData = <Map<String, dynamic>>[];
  int page = 1;
  
  while (true) {
    final response = await http.get(
      Uri.parse('$baseUrl?page=$page&limit=50')
    );
    
    if (response.statusCode != 200) break;
    
    final data = json.decode(response.body) as List;
    if (data.isEmpty) break;
    
    allData.addAll(data.cast<Map<String, dynamic>>());
    print('ページ $page 取得完了: ${data.length}件');
    page++;
    
    // API負荷軽減のための待機
    await Future.delayed(Duration(milliseconds: 100));
  }
  
  print('総取得件数: ${allData.length}件');
  return allData;
}

// ファイルアップロード
Future<void> uploadFile(String filePath, String uploadUrl) async {
  final file = File(filePath);
  
  if (!await file.exists()) {
    print('ファイルが存在しません: $filePath');
    return;
  }

  final request = http.MultipartRequest('POST', Uri.parse(uploadUrl));
  request.headers['Authorization'] = 'Bearer your-token';
  
  request.files.add(await http.MultipartFile.fromPath('file', filePath));
  request.fields['category'] = 'documents';
  request.fields['public'] = 'false';

  try {
    final streamedResponse = await request.send();
    final response = await http.Response.fromStream(streamedResponse);
    
    if (response.statusCode == 200) {
      final result = json.decode(response.body);
      print('アップロード成功: ${result['id']}');
    } else {
      print('アップロード失敗: ${response.statusCode}');
    }
    
  } catch (e) {
    print('アップロードエラー: $e');
  }
}

// ストリーミングダウンロード
Future<void> downloadFile(String url, String savePath) async {
  final request = http.Request('GET', Uri.parse(url));
  final streamedResponse = await http.Client().send(request);
  
  if (streamedResponse.statusCode != 200) {
    print('ダウンロード失敗: ${streamedResponse.statusCode}');
    return;
  }

  final file = File(savePath);
  final sink = file.openWrite();
  
  int downloaded = 0;
  final contentLength = streamedResponse.contentLength ?? 0;

  await for (final chunk in streamedResponse.stream) {
    sink.add(chunk);
    downloaded += chunk.length;
    
    if (contentLength > 0) {
      final progress = (downloaded / contentLength * 100).toInt();
      print('ダウンロード進捗: $progress%');
    }
  }
  
  await sink.close();
  print('ダウンロード完了: $savePath');
}

// レスポンスキャッシュ実装
class CacheClient extends http.BaseClient {
  final http.Client _inner;
  final Map<String, http.Response> _cache = {};
  final Duration cacheDuration;

  CacheClient(this._inner, {this.cacheDuration = const Duration(minutes: 5)});

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    // GETリクエストのみキャッシュ
    if (request.method == 'GET') {
      final cacheKey = request.url.toString();
      final cached = _cache[cacheKey];
      
      if (cached != null) {
        print('キャッシュヒット: $cacheKey');
        return http.StreamedResponse(
          Stream.value(cached.bodyBytes),
          cached.statusCode,
          headers: cached.headers,
        );
      }
    }
    
    final response = await _inner.send(request);
    
    // キャッシュに保存(簡略化実装)
    if (request.method == 'GET' && response.statusCode == 200) {
      final responseBody = await response.stream.toBytes();
      _cache[request.url.toString()] = http.Response.bytes(
        responseBody,
        response.statusCode,
        headers: response.headers,
      );
      
      // 新しいストリームを返す
      return http.StreamedResponse(
        Stream.value(responseBody),
        response.statusCode,
        headers: response.headers,
      );
    }
    
    return response;
  }
}

Flutterでの実用例

import 'package:http/http.dart' as http;
import 'dart:convert';

// Flutterアプリでのデータ取得クラス
class UserService {
  static const String _baseUrl = 'https://api.example.com';
  final http.Client _client = http.Client();

  Future<List<User>> getUsers() async {
    try {
      final response = await _client.get(
        Uri.parse('$_baseUrl/users'),
        headers: {'Accept': 'application/json'},
      );

      if (response.statusCode == 200) {
        final List<dynamic> data = json.decode(response.body);
        return data.map((json) => User.fromJson(json)).toList();
      } else {
        throw Exception('ユーザー取得失敗: ${response.statusCode}');
      }
    } catch (e) {
      throw Exception('ネットワークエラー: $e');
    }
  }

  Future<User> createUser(User user) async {
    final response = await _client.post(
      Uri.parse('$_baseUrl/users'),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: json.encode(user.toJson()),
    );

    if (response.statusCode == 201) {
      return User.fromJson(json.decode(response.body));
    } else {
      throw Exception('ユーザー作成失敗: ${response.statusCode}');
    }
  }

  void dispose() {
    _client.close();
  }
}

// ユーザーモデルクラス
class User {
  final String id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }
}

// Flutterウィジェットでの使用例
Future<void> flutterUsageExample() async {
  final userService = UserService();
  
  try {
    // ユーザー一覧取得
    final users = await userService.getUsers();
    print('取得したユーザー数: ${users.length}');
    
    // 新規ユーザー作成
    final newUser = User(
      id: '',
      name: '新規ユーザー',
      email: '[email protected]'
    );
    
    final createdUser = await userService.createUser(newUser);
    print('作成されたユーザー: ${createdUser.name}');
    
  } catch (e) {
    print('エラー: $e');
  } finally {
    userService.dispose();
  }
}