dart_http2_flutter_sse 1.0.2
dart_http2_flutter_sse: ^1.0.2 copied to clipboard
A production-ready HTTP/2 client package for Dart and Flutter with interceptors, retry, timeouts, cancellation, streaming, and connection pooling.
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.