restio 0.10.8

Dart native
Flutter Android iOS

A simple but powerful HTTP Client for Dart inspired by OkHttp.

Restio #

An HTTP Client for Dart inpired by OkHttp.

Features #

  • GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
  • Request Body can be List<int>, String, Stream, File or JSON Object.
    • Auto detects Content-Type.
    • Buffer less processing for List<int> and File.
  • Response Body gives you access as raw or decompressed data (List<int>, String and JSON Object).
    • Supports Gzip, Deflate and Brotli.
  • Easy to upload one or more file(s) via multipart/form-data.
    • Auto detects file content type.
  • Interceptors using Chain of responsibility.
  • Basic, Digest, Bearer and Hawk authorization methods.
  • Send and store cookies for your request via CookieJar.
  • Allows GET request with payload.
  • Works fine with HTTP/2 and HTTP/1.1.
    • HTTP/2 Server Push support.
  • Have Client level options and also override at Request level if you want to.
  • Caching applying RFC 7234 and Lru Replacement Strategy. (Documentation)
    • Supports encryption.
  • Custom Client Certificates.
  • Proxy settings.
  • DNS-over-UDP and DNS-over-HTTPS.
  • WebSocket and SSE.
  • Redirect Policy.
  • HTTP/HTTP2 Connection Pool.
  • HTTP client generator inspired by Retrofit. (Documentation)

Installation #

In pubspec.yaml add the following dependency:

dependencies:
  restio: ^0.10.8

How to use #

  1. Create a instance of Restio:
final client = Restio();
  1. Create a Request:
final request = Request(
    uri: RequestUri.parse('https://httpbin.org/get'),
    method: HttpMethod.get,
);

or

final request = Request.get('https://httpbin.org/get');

or

final request = get('https://httpbin.org/get');
  1. Create a Call from Request:
final call = client.newCall(request);
  1. Execute the Call and get a Response:
final response = await call.execute();
  1. At end close the response.
await response.close();
  1. To close all persistent connections and no more make requests:
await client.close();

Recipes #

Request Options #

final options = RequestOptions(
  connectTimeout : Duration(...),  // default is null (no timeout).
  writeTimeout : Duration(...),  // default is null (no timeout).
  receiveTimeout : Duration(...),  // default is null (no timeout).
  auth: BasicAuthenticator(...), // default is null (no auth).
  followRedirects: true,
  followSslRedirects: true,
  maxRedirects: 5,
  verifySSLCertificate: false,
  userAgent: 'Restio/0.10.8',
  proxy: Proxy(...), // default is null.
  dns: DnsOverHttps(...), // default is null.
  certificate: Certificate(...), // default is null.
  http2: false,
  allowServerPushes: false,
  persistentConnection: true,
  context: SecurityContext(...),  // default is null (created automatically).
  onEvent: (event) {},
);

// At Client level. (Applies to all requests)
final client = Restio(options: options);

// Override at Request Level.
final request = get('http://httpbin.org/get', options: options);

Adding Headers and Queries #

final request = Request.get(
  'https://postman-echo.com/get?a=b',
  headers: {'header-name':'header-value'}.asHeaders(),
  queries: {'query-name':'query-value'}.asQueries(),
);

You can use HeadersBuilder and QueriesBuilder too.

final builder = HeadersBuilder();
builder.add('header-name', 'header-value');
builder.set('header-name', 'header-value-2');
builder.removeAll('header-name');
final headers = builder.build();

Performing a GET request #

final client = Restio();
final request = get('https://postman-echo.com/get');
final call = client.newCall(request);
final response = await call.execute();

Performing a POST request #

final request = post(
  'https://postman-echo.com/post',
  body: 'This is expected to be sent back as part of response body.'.asBody(),
);
final call = client.newCall(request);
final response = await call.execute();

Get response stream #

final stream = response.body.data;
await response.close();

Get raw response bytes #

final bytes = await response.body.raw();
await response.close();

Get decompressed response bytes (gzip, deflate or brotli) #

final bytes = await response.body.decompressed();
await response.close();

Get response string #

final string = await response.body.string();
await response.close();

Get response JSON #

final json = await response.body.json();
await response.close();

Sending form data #

final request = post(
  'https://postman-echo.com/post',
  body: {
    'foo1': 'bar1',
    'foo2': 'bar2',
  }.asForm(),
);
final call = client.newCall(request);
final response = await call.execute();

You can use FormBuilder too.

Sending multipart data #

final request = post(
  'https://postman-echo.com/post',
  body: {'file1': File('./upload.txt')}.asMultipart(),
);
final call = client.newCall(request);
final response = await call.execute();

or

final request = post(
  'https://postman-echo.com/post',
  body: MultipartBody(
    parts: [
      // File.
      Part.file(
        // field name.
        'file1', 
        // file name.
        'upload.txt', 
        // file content.
        RequestBody.file(
          File('./upload.txt'),
          contentType: MediaType.text,
        ),
      ),
      // Text.
      Part.form('a', 'b'),
    ],
  ),
);

Posting binary data #

// Binary data.
final data = <int>[...];
final request = post(
  'https://postman-echo.com/post',
  body: data.asBody(),
);
final call = client.newCall(request);
final response = await call.execute();

Listening for download progress #

void onProgress(Response res, int received, int total, bool done) {
  print('received: $received, total: $total, done: $done');
};

final client = Restio(onDownloadProgress: onProgress);
final request = get('https://httpbin.org/stream-bytes/36001');
final call = client.newCall(request);
final response = await call.execute();
final data = await response.body.raw();
await response.close();

Listening for upload progress #

void onProgress(Request req, int sent, int total, bool done) {
  print('sent: $sent, total: $total, done: $done');
};

final client = Restio(onUploadProgress: onProgress);
final request = post('https://postman-echo.com/post',
  body: File('./large_file.txt').asBody(),
);

final call = client.newCall(request);
final response = await call.execute();

Pause & Resume retrieving response data #

final response = await call.execute();
final body = response.body;
final data = await body.raw();
await response.close();

// Called from any callback.
body.pause();
body.resume();

Interceptors #

final client = Restio(
  interceptors: const [MyInterceptor()], // Called before internal interceptors.
  networkInterceptors: const [MyInterceptor()], // Called after internal interceptor (before starting connection).
);

class MyInterceptor implements Interceptor {
  const MyInterceptor();

  @override
  Future<Response> intercept(Chain chain) async {
    final request = chain.request;
    print('Sending request: $request');
    final response = await chain.proceed(chain.request);
    print('Received response: $response');
    return response;
  }
}

You can add LogInterceptor to interceptors property to print request/response logs.

Authentication #

final client = Restio(
  options: const RequestOptions(
    authenticator: BasicAuthenticator(
      username: 'postman',
      password: 'password',
    ),
  ),
);

final request = get('https://postman-echo.com/basic-auth');

final call = client.newCall(request);
final response = await call.execute();

Supports Bearer, Digest and Hawk Authorization method too.

final client = Restio(cookieJar: const MyCookieJar());

class MyCookieJar implements CookieJar {
  const MyCookieJar();

  @override
  Future<List<Cookie>> load(Request request) async {
    // Your code.
  }

  @override
  Future<void> save(Response response) async {
    final cookies = response.cookies;
    // Your code.
  }
}

Custom Client Certificates #

final client = Restio(
  certificates: [
    Certificate(
      host: 'client.badssl.com', // Supports wildcard too!
      certificate: File('./badssl.com-client.pem').readAsBytesSync(),
      privateKey: File('./badssl.com-client.p12').readAsBytesSync(),
      port: 443, // Optional. (null matches any port)
      password: 'badssl.com',
    ),
  ],
);
final request = get('https://client.badssl.com/');
final call = client.newCall(request);
final response = await call.execute();
await response.close();

You can pass in RequestOptions too. The host and port will be ignored.

Handling Errors #

try {
  final response = await call.execute();
} on CancelledException catch(e) {
  // TODO:
} on TooManyRedirectsException catch(e) {
  // TODO:
} on TimedOutException catch(e) {
  // TODO:
} on RestioException catch(e) {
  // TODO:
}

Cancellation #

final call = client.newCall(request);
final response = await call.execute();

// Cancel the request with 'Cancelled' message. This is throw a CancelledException with the message.
call.cancel('Cancelled');

Proxy #

final client = Restio(
  options: const RequestOptions(
    proxy: Proxy(
      host: 'localhost',
      port: 3001,
    ),
  ),
);

final request = get('http://localhost:3000');
final call = client.newCall(request);
final response = await call.execute();

HTTP2 #

final client = Restio();
const options = RequestOptions(http2: true);
final request = get('https://www.google.com/', options: options);
final call = client.newCall(request);
final response = await call.execute();

HTTP2 Server Push #

final client = Restio();
const options = RequestOptions(http2: true, allowServerPushes: true);
final request = get('https://nghttp2.org/', options: options);
final call = client.newCall(request);
final response = await call.execute();

await for(final push in response.pushes) {
  final headers = push.headers;
  final response = await push.response;
  print(await response.body.string());
  await response.close();
}

WebSocket #

final client = Restio();
final request = Request(uri: RequestUri.parse('wss://echo.websocket.org'));
final ws = client.newWebSocket(request);
final conn = await ws.open();

// Receive.
conn.stream.listen((dynamic data) {
  print(data);
});

// Send.
conn.addString('🌿🐨💤');

await conn.close();

SSE #

final client = Restio();
final request = Request(uri: RequestUri.parse('https://my.sse.com'));
final sse = client.newSse(
  request,
  lastEventId: '0', // Optional. Specifies the value of the event source’s last event ID
  retryInterval: const Duration(seconds: 1), // Optional. Enables auto reconnect and specifies the interval between retry attempts.
  maxRetries: 1, // Optional. The maximum amount of retries for auto reconnect. Use null or -1 for infinite.
);
final conn = await sse.open();

// Listen.
conn.stream.listen((SseEvent event) {
  print(event.id);
  print(event.event);
  print(event.data);
});

await conn.close();

DNS #

Thanks to dart-protocol for this great dns library!

const dns = DnsOverUdp.google();
final client = Restio(options: RequestOptions(dns: dns));
final request = get('https://postman-echo.com/get');
final call = client.newCall(request);
final response = await call.execute();

print(response.address); // Prints the resolved IP address.

Supports DnsOverHttps too.

Converter #

Restio ships with default JSON Converter using the dart:convert package. You can add additional converters for other popular formats or just change the default implementation.

class FlutterBodyConverter extends BodyConverter {
  const FlutterBodyConverter();
  
  @override
  Future<String> encode<T>(
    T value,
    MediaType contentType,
  ) {
    final mimeType = contentType.mimeType;

    if (mimeType == 'application/json') {
      return compute(...);
    } else {
      throw RestioException('Content type $mimeType not supported');
    }
  }

  @override
  Future<T> decode<T>(
    String source,
    MediaType contentType,
  ) {
    final mimeType = contentType.mimeType;

    if (mimeType == 'application/json') {
      if (isType<T, User>()) {
        return compute(...);
      }
    } else {
      throw RestioException('Content type $mimeType not supported');
    }
  }
}

/// Checks whether [T1] is a type or subtype of [T2].
bool isType<T1, T2>() => <T1>[] is List<T2>;

// Set the global custom converter.
Restio.bodyConverter = const FlutterBodyConverter();

// Encoding using the custom converter.
final user = User(...);
final body = RequestBody.encode(user, contentType: MediaType.json);

// Decoding using the custom converter.
final user = await responseBody.decode<User>();

Mocking #

final mockClient = Restio(
  networkInterceptors: [
    MockInterceptor(
      [
        Response(code: 200, body: ResponseBody.string('OK')),
      ],
    ),
  ],
);

final request = Request.get('http://mock.test.io'); // Use any URI.
final call = mockClient.newCall(request);
final response = await call.execute();

expect(response.code, 200);
expect(await response.body.string(), 'OK');
await response.close();
10
likes
75
pub points
77%
popularity

Publisher

tiagohm.dev

A simple but powerful HTTP Client for Dart inspired by OkHttp.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

brotli, crypto, equatable, globbing, http2, http_parser, ip, meta, mime_type, path, raw, string_scanner, utf

More

Packages that depend on restio