HTTP
Dart公式のHTTPクライアントパッケージ。シンプルで軽量な設計により、基本的なHTTPリクエスト・レスポンス処理を提供。JSON取得、サーバーレスポンス処理、ファイルダウンロードなどFlutterアプリのネットワーキングコードの基盤となる。
GitHub概要
dart-lang/http
A composable API for making HTTP requests in Dart.
トピックス
スター履歴
ライブラリ
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();
}
}