HTTP
Official HTTP client package for Dart. Provides basic HTTP request/response processing through simple and lightweight design. Foundation for networking code in Flutter apps including JSON fetching, server response processing, and file downloading.
GitHub Overview
dart-lang/http
A composable API for making HTTP requests in Dart.
Topics
Star History
Library
http
Overview
http is "a lightweight and versatile HTTP client library for Dart" developed as the most fundamental and reliable HTTP client library in the Dart ecosystem. Positioned as "Dart's officially recommended HTTP solution," it comprehensively supports HTTP communication requirements such as RESTful API integration, data fetching, and authentication processing through a simple and intuitive Future-based API. Achieving multi-platform support across Flutter, Web, desktop, and server applications, it has established itself as the de facto standard HTTP library for Dart developers.
Details
http 2025 edition continues to maintain its overwhelming position as the foundational library for Dart HTTP communication. With long-term support from the official Dart team, it guarantees stable quality and backward compatibility, providing a consistent development experience across all Dart platforms including Flutter, server-side Dart, and web applications. Despite its lightweight design, it incorporates full-scale HTTP functionality and delivers optimal performance in each environment through platform-specific optimizations (BrowserClient, IOClient, etc.). Its composable architecture seamlessly integrates with extension libraries like RetryClient, making it capable of meeting enterprise-level HTTP communication requirements.
Key Features
- Simple Future-based API: Intuitive asynchronous HTTP processing with async/await patterns
- Multi-platform Support: Unified API for Web (BrowserClient) and mobile/desktop (IOClient)
- Composable Design: Easy custom client implementation by extending BaseClient
- Lightweight and High-Performance: Fast HTTP/1.1 communication with minimal dependencies
- Rich Extension Features: Official and third-party extensions like RetryClient, authentication, logging
- Official Dart Support: Continuous development and maintenance by the official team
Pros and Cons
Pros
- Overwhelming reliability and stability as Dart's officially recommended library
- Unified development experience across all platforms: Flutter, Web, and server-side
- Low learning cost and high development efficiency through lightweight and simple API
- Rich community support and extension library ecosystem
- Complete integration with Dart's asynchronous programming through Future-based design
- Optimal performance delivery in each environment through platform optimization
Cons
- HTTP/1.1-centered design with limited HTTP/2 and HTTP/3 functionality
- Advanced features (connection pool management, detailed cache control) require separate libraries
- Basic functionality focus requires additional implementation for complex HTTP processing
- Need to use dedicated clients to access platform-specific optimization features
- No synchronous processing API support (all asynchronous Future-based)
- Performance constraints with large-scale parallel requests
Reference Pages
Code Examples
Installation and Basic Setup
# Add http package
dart pub add http
# Check dependencies in pubspec.yaml
cat pubspec.yaml
# For Flutter projects
flutter pub add http
# Verification in Dart environment
dart pub deps
Basic HTTP Requests (GET/POST/PUT/DELETE)
import 'package:http/http.dart' as http;
import 'dart:convert';
// Basic GET request
void main() async {
// Simple GET request
final response = await http.get(Uri.parse('https://api.example.com/users'));
if (response.statusCode == 200) {
print('Status code: ${response.statusCode}');
print('Response body: ${response.body}');
// Parse as JSON
final data = json.decode(response.body);
print('Parsed data: $data');
} else {
print('Request failed: ${response.statusCode}');
}
}
// GET request with query parameters
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('Request URL: $uri');
print('Response: ${response.body}');
}
// POST request (sending JSON)
Future<void> createUser() async {
final userData = {
'name': 'John Doe',
'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('User created successfully: ${createdUser['id']}');
} else {
print('Creation failed: ${response.statusCode} - ${response.body}');
}
}
// Form data POST request
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('Login successful');
}
}
// PUT request (data update)
Future<void> updateUser(String userId) async {
final updatedData = {
'name': 'Jane Doe',
'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('Update result: ${response.statusCode}');
}
// DELETE request
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('User deleted successfully');
}
}
Custom Headers and Authentication
import 'package:http/http.dart' as http;
import 'dart:convert';
// Custom header configuration
Future<void> requestWithCustomHeaders() async {
final headers = {
'User-Agent': 'MyDartApp/1.0',
'Accept': 'application/json',
'Accept-Language': 'en-US,ja-JP',
'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: ${response.body}');
}
// Bearer Token authentication
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('Authentication successful: ${response.body}');
} else if (response.statusCode == 401) {
print('Authentication failed: Please check your token');
}
}
// Basic authentication
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 auth result: ${response.statusCode}');
}
// API Key authentication
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 auth result: ${response.statusCode}');
}
// Dynamic header construction
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;
}
Error Handling and Retry Functionality
import 'package:http/http.dart' as http;
import 'package:http/retry.dart';
import 'dart:convert';
import 'dart:io';
// Comprehensive error handling
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('Authentication error: Please check your token');
} else if (response.statusCode == 403) {
print('Permission error: Access denied');
} else if (response.statusCode == 404) {
print('Not found: Resource does not exist');
} else if (response.statusCode == 429) {
print('Rate limit: Please wait before retrying');
} else if (response.statusCode >= 500) {
print('Server error: ${response.statusCode}');
}
} on SocketException catch (e) {
print('Network error: $e');
} on FormatException catch (e) {
print('JSON parsing error: $e');
} catch (e) {
print('Unexpected error: $e');
}
return null;
}
// Manual retry implementation
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));
// Return immediately on success
if (response.statusCode == 200) {
return response;
}
// Retry for specific status codes
if (attempt < maxRetries - 1 && [429, 500, 502, 503, 504].contains(response.statusCode)) {
print('Attempt ${attempt + 1} failed. Retrying in ${delay.inSeconds} seconds...');
await Future.delayed(delay);
delay = Duration(seconds: delay.inSeconds * 2); // Backoff
continue;
}
return response;
} catch (e) {
if (attempt == maxRetries - 1) {
print('Maximum attempts reached: $e');
rethrow;
}
print('Attempt ${attempt + 1} error: $e. Retrying...');
await Future.delayed(delay);
}
}
return null;
}
// Using RetryClient for automatic retry
Future<void> retryClientExample() async {
final client = RetryClient(http.Client());
try {
final response = await client.get(
Uri.parse('https://api.example.com/unstable-endpoint')
);
print('Retry successful: ${response.statusCode}');
print('Response: ${response.body}');
} catch (e) {
print('Finally failed: $e');
} finally {
client.close();
}
}
// Custom retry logic
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);
// Success or no retry needed
if (response.statusCode < 500 || attempt == maxRetries - 1) {
return response;
}
// Wait before retry
await Future.delayed(baseDelay * (attempt + 1));
} catch (e) {
if (attempt == maxRetries - 1) rethrow;
await Future.delayed(baseDelay * (attempt + 1));
}
}
throw Exception('Maximum retry attempts exceeded');
}
}
Efficient HTTP Processing with Client
import 'package:http/http.dart' as http;
import 'dart:convert';
// Basic Client usage
Future<void> basicClientUsage() async {
final client = http.Client();
try {
// Reuse the same client for multiple requests
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 {
// Always close the client
client.close();
}
}
// Custom Client implementation (automatic User-Agent addition)
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);
}
}
// Logging 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;
}
}
// Authentication 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);
}
}
// Composed Client usage example
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('Final response: ${response.body}');
} finally {
authClient.close();
}
}
// API Client class
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();
}
// Usage example
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('User list: $users');
final newUser = await api.post('/users', data: {
'name': 'New User',
'email': '[email protected]'
});
print('Created user: $newUser');
} finally {
api.close();
}
}
Concurrent Processing and File Operations
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
// Parallel fetching of multiple URLs
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}');
}
}
// Sequential pagination fetching
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 $page completed: ${data.length} items');
page++;
// Wait to reduce API load
await Future.delayed(Duration(milliseconds: 100));
}
print('Total items fetched: ${allData.length}');
return allData;
}
// File upload
Future<void> uploadFile(String filePath, String uploadUrl) async {
final file = File(filePath);
if (!await file.exists()) {
print('File does not exist: $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('Upload successful: ${result['id']}');
} else {
print('Upload failed: ${response.statusCode}');
}
} catch (e) {
print('Upload error: $e');
}
}
// Streaming download
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('Download failed: ${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('Download progress: $progress%');
}
}
await sink.close();
print('Download completed: $savePath');
}
// Response cache implementation
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 {
// Cache only GET requests
if (request.method == 'GET') {
final cacheKey = request.url.toString();
final cached = _cache[cacheKey];
if (cached != null) {
print('Cache hit: $cacheKey');
return http.StreamedResponse(
Stream.value(cached.bodyBytes),
cached.statusCode,
headers: cached.headers,
);
}
}
final response = await _inner.send(request);
// Save to cache (simplified implementation)
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 new stream
return http.StreamedResponse(
Stream.value(responseBody),
response.statusCode,
headers: response.headers,
);
}
return response;
}
}
Practical Flutter Examples
import 'package:http/http.dart' as http;
import 'dart:convert';
// Data fetching class for Flutter apps
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('Failed to get users: ${response.statusCode}');
}
} catch (e) {
throw Exception('Network error: $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('Failed to create user: ${response.statusCode}');
}
}
void dispose() {
_client.close();
}
}
// User model class
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,
};
}
}
// Usage example in Flutter widget
Future<void> flutterUsageExample() async {
final userService = UserService();
try {
// Get user list
final users = await userService.getUsers();
print('Number of users fetched: ${users.length}');
// Create new user
final newUser = User(
id: '',
name: 'New User',
email: '[email protected]'
);
final createdUser = await userService.createUser(newUser);
print('Created user: ${createdUser.name}');
} catch (e) {
print('Error: $e');
} finally {
userService.dispose();
}
}