dart_http2_flutter_sse
A production-ready HTTP/2 client package for Dart and Flutter. Provides a clean, developer-friendly API similar to dio or http, with robust HTTP/2 support.
Features
- ✅ HTTP/2 requests: GET, POST, PUT, PATCH, DELETE
- ✅ Request/response headers, query parameters, JSON body, form body, raw bytes, and streaming body
- ✅ Response streaming for large downloads
- ✅ Connection reuse, multiplexing, and connection pooling
- ✅ TLS/ALPN where available (native platforms)
- ✅ Timeout support: connect, send, receive timeouts
- ✅ Cancellation tokens for cancelling active requests
- ✅ Retry policy with exponential backoff and jitter
- ✅ Interceptors for request, response, and error handling
- ✅ Logging interceptor with safe redaction for tokens/passwords
- ✅ Certificate pinning support (native platforms)
- ✅ Graceful fallback — HTTP/1.1 on Web, with clear platform documentation
- ✅ Strong typing, null safety, no unnecessary dependencies
- ✅ Fully tested with unit and integration tests
Platform Support
| Platform | HTTP/2 Support | Notes |
|---|---|---|
| Android | ✅ Native | Via dart:io HttpClient |
| iOS | ✅ Native | Via dart:io HttpClient |
| macOS | ✅ Native | Via dart:io HttpClient |
| Linux | ✅ Native | Via dart:io HttpClient |
| Windows | ✅ Native | Via dart:io HttpClient |
| Web | ⚠️ Limited | Uses browser's fetch (HTTP/1.1). Browser may negotiate HTTP/2 but no direct control. No certificate pinning, no streaming uploads. |
Installation
Add to your pubspec.yaml:
dependencies:
dart_http2_flutter_sse: ^1.0.0
Then run:
flutter pub get
Quick Start
import 'package:dart_http2_flutter_sse/dart_http2_flutter_sse.dart';
final client = Http2Client();
final response = await client.get(
'https://api.example.com/users',
queryParameters: {'page': 1},
);
print(response.statusCode); // 200
print(response.data); // Decoded JSON body
Usage
Basic Requests
// GET
final getRes = await client.get('https://api.example.com/items');
// POST with JSON body
final postRes = await client.post(
'https://api.example.com/items',
body: {'name': 'New Item', 'price': 29.99},
);
// POST with form data
final formRes = await client.post(
'https://api.example.com/login',
body: {'username': 'john', 'password': 'secret'},
bodyType: BodyType.form,
);
// PUT
final putRes = await client.put(
'https://api.example.com/items/1',
body: {'name': 'Updated Item'},
);
// PATCH
final patchRes = await client.patch(
'https://api.example.com/items/1',
body: {'price': 19.99},
);
// DELETE
final deleteRes = await client.delete('https://api.example.com/items/1');
Query Parameters & Headers
final response = await client.get(
'https://api.example.com/search',
queryParameters: {
'q': 'flutter',
'page': 1,
'limit': 20,
},
headers: {
'Authorization': 'Bearer your-token',
'X-Custom': 'value',
},
);
Response Handling
final response = await client.get('https://api.example.com/data');
print(response.statusCode); // 200
print(response.isOk); // true
print(response.bodyString); // Raw body string
print(response.data); // Auto-decoded JSON (Map, List, or String)
print(response.dataAsMap); // Null-safe JSON map access
print(response.headers); // Response headers
print(response.contentLength); // Content-Length from headers
print(response.duration); // Request duration
Streaming Download
final request = Http2Request.get('https://example.com/large-file.zip');
int totalBytes = 0;
await for (final chunk in client.streamResponse(request)) {
totalBytes += chunk.bodyBytes?.length ?? 0;
print('Received $totalBytes bytes so far...');
}
print('Download complete: $totalBytes bytes');
Streaming Upload
final fileStream = File('large-file.txt').openRead();
final response = await client.post(
'https://api.example.com/upload',
body: fileStream,
bodyType: BodyType.stream,
onSendProgress: (sent, total) {
print('Uploaded $sent of $total bytes');
},
);
Timeouts
final client = Http2Client(
timeoutConfiguration: const TimeoutConfiguration(
connectTimeout: Duration(seconds: 10),
sendTimeout: Duration(seconds: 30),
receiveTimeout: Duration(seconds: 30),
),
);
// Or use the factory for all-equal timeouts:
final client2 = Http2Client(
timeoutConfiguration: TimeoutConfiguration.all(
Duration(seconds: 15),
),
);
Cancellation
final cts = CancellationTokenSource();
// Start a request
final future = client.get(
'https://api.example.com/slow-endpoint',
cancellationToken: cts.token,
);
// Cancel it later
cts.cancel();
try {
await future;
} on CancelledException catch (e) {
print('Request was cancelled: $e');
}
Retry with Exponential Backoff
final client = Http2Client(
retryConfig: const RetryConfig(
maxRetries: 3,
baseDelay: Duration(seconds: 1),
maxDelay: Duration(seconds: 30),
useJitter: true, // Adds randomness to avoid thundering herd
retryableStatusCodes: {
408, // Request Timeout
429, // Too Many Requests
500, // Internal Server Error
502, // Bad Gateway
503, // Service Unavailable
504, // Gateway Timeout
},
retryOnConnectionErrors: true,
),
);
Custom Retry Logic
final client = Http2Client(
retryConfig: RetryConfig(
maxRetries: 2,
retryOnError: (error) {
// Only retry on specific conditions
if (error is TimeoutException) return true;
if (error is HttpStatusException && error.statusCode == 429) return true;
return false;
},
),
);
Interceptors
// Logging interceptor (safe redaction built-in)
final client = Http2Client(
interceptors: [
LoggingInterceptor(const LoggingConfig(
logRequest: true,
logResponse: true,
logRequestBody: false, // Off by default for security
logResponseBody: false, // Off by default for security
logErrors: true,
)),
],
);
// Custom interceptor
class AuthInterceptor extends Interceptor {
final String token;
AuthInterceptor(this.token);
@override
void onRequest(Http2Request request, RequestHandler handler) {
request.headers['Authorization'] = 'Bearer $token';
handler.next(request);
}
@override
void onResponse(Http2Response response, ResponseHandler handler) {
print('Response: ${response.statusCode}');
handler.next(response);
}
@override
void onError(Http2Exception error, ErrorHandler handler) {
print('Error: $error');
handler.next(error);
}
}
final client = Http2Client(
interceptors: [
AuthInterceptor('your-token'),
LoggingInterceptor(),
],
);
Certificate Pinning
final client = Http2Client(
enableCertificatePinning: true,
pinnedCertificates: {
'api.example.com': [
'-----BEGIN CERTIFICATE-----\nMIIF...',
],
},
);
Note: Certificate pinning is only supported on native platforms (Android, iOS, macOS, Linux, Windows). Not available on Web.
Error Handling
try {
final response = await client.get('https://api.example.com/data');
} on HttpStatusException catch (e) {
print('HTTP error: ${e.statusCode} - ${e.message}');
print('Response body: ${e.response?.bodyString}');
} on TimeoutException catch (e) {
print('${e.timeoutType} timeout exceeded');
} on ConnectionException catch (e) {
print('Connection failed to ${e.host}: ${e.message}');
} on CancelledException catch (e) {
print('Request was cancelled');
} on CertificatePinningException catch (e) {
print('Certificate pin mismatch for ${e.hostname}');
} on Http2Exception catch (e) {
print('Other HTTP/2 error: ${e.message}');
}
Progress Tracking
final response = await client.get(
'https://api.example.com/large-file',
onReceiveProgress: (received, total) {
final progress = total > 0 ? (received / total * 100).toStringAsFixed(1) : '?';
print('Downloaded $received / $total bytes ($progress%)');
},
);
Architecture
lib/
├── dart_http2_flutter_sse.dart # Barrel export
├── src/
│ ├── client.dart # Http2Client public API
│ ├── models/
│ │ ├── http2_request.dart # Request model with body serialization
│ │ ├── http2_response.dart # Response model with JSON decoding
│ │ └── http2_exceptions.dart # Typed exception hierarchy
│ ├── interceptors/
│ │ ├── interceptor.dart # Interceptor interface + chain
│ │ ├── logging_interceptor.dart # Request/response logging with redaction
│ │ └── retry_interceptor.dart # Retry with exponential backoff
│ ├── transport/
│ │ ├── transport.dart # Abstract transport interface
│ │ ├── http2_transport.dart # HTTP/2 transport (native)
│ │ ├── http1_fallback_transport.dart # HTTP/1.1 fallback
│ │ └── web_transport_stub.dart # Web platform transport
│ └── utils/
│ ├── cancellation_token.dart # Cancellation token system
│ ├── timeout_manager.dart # Timeout enforcement
│ └── connection_pool.dart # Connection pooling
Comparison: HTTP/2 vs HTTP/1.1
| Feature | HTTP/2 (this package) | HTTP/1.1 (http package) |
|---|---|---|
| Multiplexing | ✅ Multiple streams over single connection | ❌ One request at a time per connection |
| Header compression | ✅ HPACK compression | ❌ Plain text headers |
| Server push | ✅ Supported | ❌ Not supported |
| Binary protocol | ✅ Binary framing | ❌ Text-based |
| Connection reuse | ✅ Connection pooling built-in | ⚠️ Limited (keep-alive) |
| Stream prioritization | ✅ Supported | ❌ Not supported |
| TLS requirement | ⚠️ Most implementations require TLS | ✅ Works over plain HTTP |
| Latency | ✅ Lower (fewer round trips) | ❌ Higher (head-of-line blocking) |
| Web support | ⚠️ Limited (browser negotiation) | ✅ Full support |
| Certificate pinning | ✅ Supported (native) | ❌ Not available |
Comparison with Dio
| Feature | dart_http2_flutter_sse | Dio |
|---|---|---|
| HTTP/2 focus | ✅ First-class HTTP/2 support | ⚠️ HTTP/1.1 with h2 via adapter |
| Package size | ✅ Minimal dependencies | ❌ Heavier (crypto, etc.) |
| Interceptors | ✅ Full interceptor chain | ✅ Full interceptor chain |
| Retry | ✅ Built-in with exponential backoff | ⚠️ Via add-on package |
| Cancellation | ✅ Cancellation tokens | ✅ CancelToken |
| Certificate pinning | ✅ Built-in | ⚠️ Via custom adapter |
| Streaming | ✅ Response streaming | ✅ Via ResponseType.stream |
| Web support | ✅ HTTP/1.1 fallback | ✅ Full support |
| pub.flutter-io.cn popularity | New | ✅ Very popular |
Running Tests
# Run unit tests
flutter test
# Run integration tests (requires network)
flutter test --dart-define=RUN_INTEGRATION_TESTS=true integration_test/
Limitations
- Web platform: HTTP/2 is not directly controllable from Dart on the web. The browser may negotiate HTTP/2 with the server, but this package falls back to HTTP/1.1 semantics. Certificate pinning and streaming uploads are not available on Web.
- ALPN negotiation: On native platforms, ALPN (Application-Layer Protocol Negotiation) is handled by the underlying TLS library. If the server does not support HTTP/2, the connection falls back to HTTP/1.1.
- Server push: While HTTP/2 supports server push, this client does not currently expose a push-specific API. Pushed resources are handled transparently.
- Flow control: HTTP/2 stream flow control is handled by the underlying implementation and is not directly configurable through this package.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Libraries
- dart_http2_flutter_sse
- A production-ready HTTP/2 client package for Dart and Flutter.