{"owner":"Notsfsssf","repo":"pixez-flutter","hasSkills":true,"totalSkillsCount":6,"totalTokensCount":7106,"categories":["plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":["plugins/rhttp/README.md","plugins/rhttp/benchmark/README.md","plugins/rhttp/rhttp/README.md","plugins/rhttp/rhttp/cargokit/build_tool/README.md","plugins/rhttp/rhttp/example/README.md","plugins/rhttp/rhttp/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md"],"skills":{"plugins/rhttp/README.md":"rhttp/README.md","plugins/rhttp/benchmark/README.md":"# benchmark\n\nStart server:\n\n```shell\ncd nodejs\nnode server.js\n```\n\nStart benchmark:\n\n```shell\ncd benchmark\nflutter run --release\n```\n\n## 1 KB x 10000\n- rhttp: 1010 ms\n- http: 2174 ms\n- dio: 2758 ms\n\n## 10 MB x 100\n- rhttp: 2394 ms\n- http: 12527 ms\n- dio: 13091 ms\n","plugins/rhttp/rhttp/README.md":"# rhttp\n\n[![pub package](https://img.shields.io/pub/v/rhttp.svg)](https://pub.dev/packages/rhttp)\n![ci](https://codeberg.org/Tienisto/rhttp/actions/workflows/ci.yml/badge.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nMake HTTP requests using Rust for Flutter developers.\n\n## About\n\nThis package is a Dart wrapper around the [reqwest](https://crates.io/crates/reqwest) crate, which is a fast and reliable HTTP client for Rust.\nFor optimal performance, we use FFI with [flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge) to call Rust code.\n\nThe default HTTP client in Dart is part of `dart:io`, which lacks configurability and performance compared to other HTTP clients.\nFurthermore, HTTP/2 and HTTP/3 are either missing or not supported by default.\n\nCompared to [cronet_http](https://pub.dev/packages/cronet_http) and [cupertino_http](https://pub.dev/packages/cupertino_http), this package offers a unified, feature-rich API\nthat also works on Windows and Linux.\n\nThe APK size will increase by 2 MB on arm64 and 6 MB if compiled for all architectures (x64, arm32, arm64).\n\nWeb is currently not supported.\n\n## Features\n\n- ✅ HTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 support\n- ✅ TLS 1.2 and 1.3 support\n- ✅ Connection pooling\n- ✅ Interceptors\n- ✅ Retry (optional)\n- ✅ Certificate pinning\n- ✅ Proxy support\n- ✅ Custom DNS resolution\n- ✅ Cookies\n- ✅ Strong type safety\n- ✅ DevTools support ([Network tab](https://docs.flutter.dev/tools/devtools/network))\n- ✅ Compatible with [dart:io](https://api.dart.dev/stable/dart-io/HttpClient-class.html), [http](https://pub.dev/packages/http), and [dio](https://pub.dev/packages/dio)\n\n## Benchmark\n\nrhttp is much faster at downloading large files and a bit faster at downloading small files compared to the default HTTP client in Dart.\n\n| Small Files (1 KB)                                                                                 | Large Files (10 MB)                                                                                |\n|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|\n| ![benchmark-small](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-small.png) | ![benchmark-large](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-large.png) |\n\nReferred packages: [dio](https://pub.dev/packages/dio) (5.5.0+1), [http](https://pub.dev/packages/http) (1.2.2), [rhttp](https://pub.dev/packages/rhttp) (0.3.0)\n\nCheckout the benchmark code [here](https://github.com/Tienisto/rhttp/tree/main/benchmark).\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Request Basics](#request-basics)\n  - [HTTP methods](#-http-methods)\n  - [Request query parameters](#-request-query-parameters)\n  - [Request Headers](#-request-headers)\n  - [Request Body](#-request-body)\n  - [Response Body](#-response-body)\n- [Request Lifecycle](#request-lifecycle)\n  - [Cancel Requests](#-cancel-requests)\n  - [Progress](#-progress)\n- [Client Settings](#client-settings)\n  - [Connection Reuse](#-connection-reuse)\n  - [Keep-Alive](#-keep-alive)\n  - [Timeout](#-timeout)\n  - [Base URL](#-base-url)\n  - [HTTP version](#-http-version)\n  - [TLS version](#-tls-version)\n  - [TLS Server Name Indication (SNI)](#-tls-server-name-indication-sni)\n  - [Certificate Pinning](#-certificate-pinning)\n  - [Root certificate source](#-root-certificate-source)\n  - [Client Authentication](#-client-authentication--mutual-tls)\n  - [Disable certificate verification](#-disable-certificate-verification)\n  - [Proxy](#-proxy)\n  - [Redirects](#-redirects)\n  - [DNS resolution](#-dns-resolution)\n  - [Cookies](#-cookies)\n  - [User-Agent](#-user-agent)\n- [Intercept](#intercept)\n  - [Interceptors](#-interceptors)\n  - [RetryInterceptor](#-retryinterceptor)\n- [Error Handling](#error-handling)\n  - [Exceptions](#-exceptions)\n  - [Throw on Status Code](#-throw-on-status-code)\n- [Compatibility Layer](#compatibility-layer)\n\n## Getting Started\n\n### ➤ Installation\n\n1. Install Rust via [rustup](https://rustup.rs/).\n   - Rust 1.80.0 or later is required.\n2. For Android: Install [Command-line tools](https://stackoverflow.com/questions/68236007/i-am-getting-error-cmdline-tools-component-is-missing-after-installing-flutter)\n   - Make sure to have the latest NDK installed. [#44](https://github.com/Tienisto/rhttp/issues/44)\n3. Add `rhttp` to `pubspec.yaml`:\n\n```yaml\ndependencies:\n  rhttp: <version>\n```\n\n### ➤ Initialization\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init(); // add this\n  runApp(MyApp());\n}\n```\n\n### ➤ Usage\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  // Make a GET request\n  HttpTextResponse response = await Rhttp.get('https://example.com');\n  \n  // Read the response\n  int statusCode = response.statusCode;\n  String body = response.body;\n}\n```\n\nAlternatively, you can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package.\n\nFor more information, see [Compatibility Layer](#compatibility-layer).\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\n## Request Basics\n\n### ➤ HTTP methods\n\nYou can make requests using different HTTP methods:\n\n```dart\n// Pass the method as an argument\nawait Rhttp.requestText(method: HttpMethod.post, url: 'https://example.com');\n\n// Use the helper methods\nawait Rhttp.post('https://example.com');\n```\n\n### ➤ Request query parameters\n\nYou can add query parameters to the URL:\n\n```dart\nawait Rhttp.get('https://example.com', query: {'key': 'value'});\n```\n\n### ➤ Request Headers\n\nYou can add headers to the request:\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  headers: const HttpHeaders.map({\n    HttpHeaderName.contentType: 'application/json',\n  }),\n);\n```\n\n### ➤ Request Body\n\nYou can add a body to the request. There are different types of bodies you can use:\n\n**Text**\n\nPass a string to the `HttpBody.text` constructor.\n\n```dart\n// Raw body\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.text('raw body'),\n);\n```\n\n**JSON**\n\nPass a JSON data structure to the `HttpBody.json` constructor.\n\nThe Content-Type header will be set to `application/json` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.json({'key': 'value'}),\n);\n```\n\n**Binary**\n\nPass a `Uint8List` to the `HttpBody.bytes` constructor.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(Uint8List.fromList([0, 1, 2])),\n);\n```\n\n**Stream**\n\nPass a `Stream<List<int>>` to the `HttpBody.stream` constructor.\n\nIt is recommended to also provide a `length` to automatically set the `Content-Length` header.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.stream(\n    Stream.fromIterable([[1, 2, 3]]),\n    length: 3,\n  ),\n);\n```\n\n**Form**\n\nPass a flat map to the `HttpBody.form` constructor.\n\nThe Content-Type header will be set to `application/x-www-form-urlencoded` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.form({'key': 'value'}),\n);\n```\n\n**Multipart**\n\nPass a map of `MultipartItem` to the `HttpBody.multipart` constructor.\n\nThe Content-Type header will be overridden to `multipart/form-data` with a random boundary.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.multipart({\n    'name': const MultipartItem.text(\n      text: 'Tom',\n    ),\n    'profile_image': MultipartItem.bytes(\n      bytes: Uint8List.fromList(bytes),\n      fileName: 'image.jpeg',\n    ),\n  }),\n)\n```\n\n### ➤ Response Body\n\nTo let Rust do most of the work, you must specify the expected response body type before making the request.\n\n```dart\nHttpTextResponse response = await Rhttp.getText('https://example.com');\nString body = response.body;\n\nHttpBytesResponse response = await Rhttp.getBytes('https://example.com');\nUint8List body = response.body;\n\nHttpStreamResponse response = await Rhttp.getStream('https://example.com');\nStream<Uint8List> body = response.body;\n```\n\nThey all extend the `HttpResponse` class, which contains the following properties:\n\n| Property                                  | Description                                                     |\n|-------------------------------------------|-----------------------------------------------------------------|\n| `String? remoteIp`                        | The remote IP address of the server that sent the response.     |\n| `HttpRequest request`                     | The HTTP request that this response is associated with.         |\n| `HttpVersion version`                     | The HTTP version of this response.                              |\n| `int statusCode`                          | The HTTP status code of this response.                          |\n| `List<(String, String)> headers`          | The HTTP headers of this response.                              |\n| `Map<String, String> headerMap`           | Response headers converted as a map.                            |\n| `Map<String, List<String>> headerMapList` | Response headers converted as a map respecting multiple values. |\n\n## Request Lifecycle\n\n### ➤ Cancel Requests\n\nYou can cancel a request by providing a `CancelToken`.\n\nIf the same `CancelToken` is used for multiple requests, all requests will be canceled.\n\nIf a canceled `CancelToken` is used for a request, the request will be canceled immediately.\n\n```dart\nfinal cancelToken = CancelToken();\nfinal request = Rhttp.get(\n   'https://example.com',\n   cancelToken: cancelToken,\n);\n\n// Cancel the request\ncancelToken.cancel();\n\n// Will throw a `RhttpCancelException`\nawait request;\n```\n\n### ➤ Progress\n\nYou can observe the progress of the request, by providing `onSendProgress` and `onReceiveProgress` callbacks.\n\nPlease note that request and response bodies must be either `Stream` or `Uint8List`.\n\nThe parameter `total` can be `-1` if the total size is unknown.\n\nIt always emits the final value with `sent` / `received` and `total` being equal after the request is finished.\n\n```dart\nfinal request = Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(bytes),\n  onSendProgress: (sent, total) {\n    print('Sent: $sent, Total: $total');\n  },\n  onReceiveProgress: (received, total) {\n    print('Received: $received, Total: $total');\n  },\n);\n```\n\n## Client Settings\n\n### ➤ Connection Reuse\n\nTo improve performance, it is recommended to create a client and reuse it for multiple requests.\n\nThis allows you to reuse connections (with same servers).\nFurthermore, it avoids the overhead of creating a new client for each request.\n\n```dart\nfinal client = await RhttpClient.create();\n\nawait client.get('https://example.com');\n```\n\nYou can dispose the client when you are done with it:\n\n```dart\nclient.dispose();\n```\n\nTo create a client synchronously, use `RhttpClient.createSync`.\nThis should only be called during app start to avoid blocking the UI thread.\n\n```dart\nfinal client = RhttpClient.createSync();\n```\n\n### ➤ Keep-Alive\n\nBy default, connections are not kept alive. On HTTP/2, the same connection\nis reused for multiple requests that are done on the same time, but the socket\nis closed immediately after the last request is finished.\n\nSetting `keepAliveTimeout` to a value greater than `0` will keep the socket \nopen when idle for the specified duration, both in HTTP/1.1 and HTTP/2.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      keepAliveTimeout: Duration(seconds: 60),\n      keepAlivePing: Duration(seconds: 30),\n    ),\n  ),\n);\n```\n\n### ➤ Timeout\n\nYou can specify the timeout for the request:\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      timeout: Duration(seconds: 10),\n      connectTimeout: Duration(seconds: 5),\n    ),\n  ),\n);\n```\n\n### ➤ Base URL\n\nAdd a base URL to the client to avoid repeating the same URL or to change the base URL easily.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    baseUrl: 'https://example.com',\n  ),\n);\n```\n\n### ➤ HTTP version\n\nYou can specify the HTTP version to use for the request.\nHTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    httpVersionPref: HttpVersionPref.http3,\n  ),\n);\n```\n\n### ➤ TLS version\n\nYou can specify the TLS version to use for the request.\nOnly TLS 1.2 and 1.3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      minTlsVersion: TlsVersion.tls12,\n      maxTlsVersion: TlsVersion.tls13,\n    ),\n  ),\n);\n```\n\n### ➤ TLS Server Name Indication (SNI)\n\nControls the use of TLS server name indication.\n\nThis option is enabled by default.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      sni: false,\n    ),\n  ),\n);\n```\n\n### ➤ Certificate Pinning\n\nTo improve security, you can specify the expected server certificate.\n\nDue to limitations on Rust's side ([Github Issue](https://github.com/seanmonstar/reqwest/issues/298)),\nyou need to either provide the full certificate chain, or the root certificate.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      trustedRootCertificates: [\n        '''-----BEGIN CERTIFICATE-----\nsome certificate\n-----END CERTIFICATE-----''',\n],\n    ),\n  ),\n);\n```\n\n### ➤ Root certificate source\n\nBy default, the root certificates provided by Mozilla (webpki) are used.\nAs of now, this is the most reliable option which requires no additional setup.\n\nYou can configure which root certificates are trusted by setting `TlsSettings.rootCertSource`.\n\n| Mode       | Description                                             | Notes                                                                                                   |\n|------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------|\n| `platform` | Use root certificates provided by the operating system. | Flexible, but may be inconsistent across platforms.                                                     |\n| `webpki`   | Use root certificates provided by Mozilla. (default)    | Consistent across platforms, but requires manual app updates. Root certs are valid for around 15 years. |\n| `none`     | Don't trust any root certificates.                      | Special use cases                                                                                       |\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      rootCertSource: RootCertSource.none,\n    ),\n  ),\n);\n```\n\n#### Android Proguard Exclusions (only required for `RootCertSource.platform`)\n\nThe `platform` mode relies on [`rustls-platform-verifier`](https://github.com/rustls/rustls-platform-verifier#proguard),\nwhose Java classes are stripped by Android's [R8 shrinker](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization)\nin release builds. The `webpki` (default) and `none` modes do not use these classes and need no Proguard configuration.\n\nIf you use `RootCertSource.platform`, add a `android/app/proguard-rules.pro` file if it does not already exist\nand add or append the following line:\n```\n-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; }\n```\nIf the proguard file did not exist earlier it must also be added to `android/app/build.gradle.kts`:\n```diff\n android {\n    ...\n    buildTypes {\n        release {\n            ...\n   \n+            proguardFiles(\n+                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n+                \"proguard-rules.pro\"\n             )\n         }\n     }\n}\n```\n\n### ➤ Client Authentication / mutual TLS\n\nYou can specify the client certificate and key to enable mutual TLS (mTLS).\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      clientCertificate: ClientCertificate(\n         certificate: clientCert,\n         privateKey: clientKey,\n      ),\n    ),\n  ),\n);\n```\n\n### ➤ Disable certificate verification\n\nThis is very insecure and should only be used for testing purposes.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      verifyCertificates: false,\n    ),\n  ),\n);\n```\n\n### ➤ Proxy\n\nBy default, the system proxy is enabled.\n\nDisable system proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.noProxy(),\n  ),\n);\n```\n\nUse a custom proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.proxy('http://localhost:8080'),\n  ),\n);\n```\n\nOnly proxy unencrypted HTTP traffic:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.static(\n      url: 'http://localhost:8080',\n      condition: ProxyCondition.onlyHttp,\n    ),\n  ),\n);\n```\n\nChain multiple proxies:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.list([\n      StaticProxy(\n        url: 'http://localhost:8080',\n        condition: ProxyCondition.onlyHttp,\n      ),\n      StaticProxy(\n        url: 'http://localhost:8081',\n        condition: ProxyCondition.onlyHttps,\n      ),\n    ]),\n  ),\n);\n```\n\n### ➤ Redirects\n\nBy default, up to 10 redirects (e.g. HTTP 302) are followed.\n\nExceeding the maximum number of redirects will throw a `RhttpRedirectException`.\n\nYou can change the maximum number of redirects and whether to follow redirects:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    redirectSettings: RedirectSettings.limited(5), // or RedirectSettings.none()\n  ),\n);\n```\n\n### ➤ DNS resolution\n\nBy default, the system DNS resolver is used.\n\nYou can override the mapping of hostnames to IP addresses:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1'],\n      },\n    ),\n  )\n);\n```\n\nFor a more complex DNS resolution, you can construct a `DnsSettings.dynamic` object:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: ClientSettings(\n    dnsSettings: DnsSettings.dynamic(\n      resolver: (String host) async {\n        if (counter % 2 == 0) {\n          return ['127.0.0.1'];\n        } else {\n          return ['1.2.3.4'];\n        }\n      }\n    ),\n  )\n);\n```\n\nBy default, the conventional port is used. You can override this behaviour by specifying the port:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1:8080'],\n      },\n    ),\n  )\n);\n```\n\n### ➤ Cookies\n\nIt is possible to optionally activate automatic Cookie handling. This will store Cookies sent by the\nserver in an ephemeral Cookie [`Jar`](https://docs.rs/reqwest/latest/reqwest/cookie/struct.Jar.html).\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    cookieSettings: CookieSettings(storeCookies: true),\n  ),\n);\n```\n\n### ➤ User-Agent\n\nA convenient way to set the `User-Agent` header.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    userAgent: 'MyApp/1.0',\n  ),\n);\n```\n\n## Intercept\n\n### ➤ Interceptors\n\nYou can add interceptors to the client to modify requests / responses, handle errors, observe requests, etc.\n\nAny exception thrown by an interceptor that is not a subclass of `RhttpException`\nwill be caught and wrapped in a `RhttpInterceptorException`.\n\n```dart\nclass TestInterceptor extends Interceptor {\n  @override\n  Future<InterceptorResult<HttpRequest>> beforeRequest(\n    HttpRequest request,\n  ) async {\n    return Interceptor.next(request.addHeader(\n      name: HttpHeaderName.accept,\n      value: 'application/json',\n    ));\n  }\n\n  @override\n  Future<InterceptorResult<HttpResponse>> afterResponse(\n    HttpResponse response,\n  ) async {\n    return Interceptor.next();\n  }\n\n  @override\n  Future<InterceptorResult<RhttpException>> onError(\n    RhttpException exception,\n  ) async {\n    return Interceptor.next();\n  }\n}\n```\n\nThere are 4 termination methods:\n\n- `Interceptor.next()`: Continue with the next interceptor.\n- `Interceptor.stop()`: Stop the interceptor chain.\n- `Interceptor.resolve()`: Resolve the request with the given response.\n- `throw RhttpException`: Throw an exception. The stack trace will be preserved.\n\nInstead of implementing the `Interceptor` class, you can use the `SimpleInterceptor` class:\n\n```dart\nfinal client = await RhttpClient.create(\n  interceptors: [\n    SimpleInterceptor(\n      onError: (exception) async {\n        if (exception is RhttpStatusCodeException && exception.statusCode == 401) {\n          // Log out\n        }\n        return Interceptor.next();\n      },\n    ),\n  ],\n);\n```\n\n### ➤ RetryInterceptor\n\nThere is a built-in `RetryInterceptor` that retries the request if it fails.\n\nDuring the retry, all interceptors except `RetryInterceptor` are called again.\n\n```dart\nclass RefreshTokenInterceptor extends RetryInterceptor {\n  final Ref ref;\n\n  RefreshTokenInterceptor(this.ref);\n\n  @override\n  int get maxRetries => 1;\n\n  @override\n  bool shouldRetry(HttpResponse? response, RhttpException? exception) {\n    return exception is RhttpStatusCodeException &&\n        (exception.statusCode == 401 || exception.statusCode == 403);\n  }\n\n  @override\n  Future<HttpRequest?> beforeRetry(\n    int attempt,\n    HttpRequest request,\n    HttpResponse? response,\n    RhttpException? exception,\n  ) async {\n    ref.read(authProvider.notifier).state = await refresh();\n    return null;\n  }\n}\n```\n\nCheckout this [example](https://github.com/Tienisto/rhttp/blob/main/rhttp/example/lib/interceptor_riverpod.dart)\nto see how access tokens can be refreshed using Riverpod.\n\n## Error Handling\n\n### ➤ Exceptions\n\nAll exceptions are subclasses of `RhttpException`.\n\nThe following exceptions can be thrown:\n\n| Exception                          | Description                                           |\n|------------------------------------|-------------------------------------------------------|\n| `RhttpCancelException`             | Request was canceled.                                 |\n| `RhttpTimeoutException`            | Request timed out.                                    |\n| `RhttpRedirectException`           | Too many redirects.                                   |\n| `RhttpStatusCodeException`         | Response has 4xx or 5xx status code.                  |\n| `RhttpInvalidCertificateException` | Server certificate is invalid.                        |\n| `RhttpConnectionException`         | Connection error. (no internet, server not reachable) |\n| `RhttpClientDisposedException`     | Client is already disposed.                           |\n| `RhttpInterceptorException`        | Interceptor threw an exception.                       |\n| `RhttpUnknownException`            | Unknown error occurred.                               |\n\n### ➤ Throw on Status Code\n\nBy default, an exception is thrown if the response has a 4xx or 5xx status code.\nYou can disable this behavior by setting `throwOnStatusCode` to `false`.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    throwOnStatusCode: false,\n  ),\n);\n```\n\n## Compatibility Layer\n\nYou can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package,\nthereby exposing the same API as the default HTTP client in the Dart ecosystem.\n\nThis comes with some downsides, such as:\n\n- inferior type safety due to the flaw that `body` is of type `Object?` instead of an explicit type\n- body of type `Map` is implicitly interpreted as `x-www-form-urlencoded` that cannot be changed\n- no support for cancellation\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\nBecause this client is compatible with [http](https://pub.dev/packages/http),\nyou can use [dio_compatibility_layer](https://pub.dev/packages/dio_compatibility_layer)\nto use rhttp with the [dio](https://pub.dev/packages/dio) package.\n\n```dart\nFuture<Dio> createDioClient() async {\n  final dio = Dio();\n  final compatibleClient = await RhttpCompatibleClient.create(); // or createSync()\n  dio.httpClientAdapter = ConversionLayerAdapter(compatibleClient);\n  return dio;\n}\n```\n\nIf you are looking for a replacement for `HttpClient` of `dart:io`, you can use the `IoCompatibleClient`:\n\n```dart\nimport 'dart:io';\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  final client = await IoCompatibleClient.create();\n  final request = await client.getUrl(Uri.parse('https://example.com'));\n  final response = await request.close();\n\n  print(response.statusCode);\n  print(await response.transform(utf8.decoder).join());\n}\n```\n\n## License\n\nMIT License\n\nCopyright (c) 2024-2026 Tien Do Nam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","plugins/rhttp/rhttp/cargokit/build_tool/README.md":"/// This is copied from Cargokit (which is the official way to use it currently)\n/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin\n\nA sample command-line application with an entrypoint in `bin/`, library code\nin `lib/`, and example unit test in `test/`.\n","plugins/rhttp/rhttp/example/README.md":"# rhttp_example\n\nDemonstrates how to use the rhttp plugin.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)\n\nFor help getting started with Flutter development, view the\n[online documentation](https://docs.flutter.dev/), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n","plugins/rhttp/rhttp/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md":"# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images."},"files":{"plugins/rhttp/README.md":"rhttp/README.md","plugins/rhttp/benchmark/README.md":"# benchmark\n\nStart server:\n\n```shell\ncd nodejs\nnode server.js\n```\n\nStart benchmark:\n\n```shell\ncd benchmark\nflutter run --release\n```\n\n## 1 KB x 10000\n- rhttp: 1010 ms\n- http: 2174 ms\n- dio: 2758 ms\n\n## 10 MB x 100\n- rhttp: 2394 ms\n- http: 12527 ms\n- dio: 13091 ms\n","plugins/rhttp/rhttp/README.md":"# rhttp\n\n[![pub package](https://img.shields.io/pub/v/rhttp.svg)](https://pub.dev/packages/rhttp)\n![ci](https://codeberg.org/Tienisto/rhttp/actions/workflows/ci.yml/badge.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nMake HTTP requests using Rust for Flutter developers.\n\n## About\n\nThis package is a Dart wrapper around the [reqwest](https://crates.io/crates/reqwest) crate, which is a fast and reliable HTTP client for Rust.\nFor optimal performance, we use FFI with [flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge) to call Rust code.\n\nThe default HTTP client in Dart is part of `dart:io`, which lacks configurability and performance compared to other HTTP clients.\nFurthermore, HTTP/2 and HTTP/3 are either missing or not supported by default.\n\nCompared to [cronet_http](https://pub.dev/packages/cronet_http) and [cupertino_http](https://pub.dev/packages/cupertino_http), this package offers a unified, feature-rich API\nthat also works on Windows and Linux.\n\nThe APK size will increase by 2 MB on arm64 and 6 MB if compiled for all architectures (x64, arm32, arm64).\n\nWeb is currently not supported.\n\n## Features\n\n- ✅ HTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 support\n- ✅ TLS 1.2 and 1.3 support\n- ✅ Connection pooling\n- ✅ Interceptors\n- ✅ Retry (optional)\n- ✅ Certificate pinning\n- ✅ Proxy support\n- ✅ Custom DNS resolution\n- ✅ Cookies\n- ✅ Strong type safety\n- ✅ DevTools support ([Network tab](https://docs.flutter.dev/tools/devtools/network))\n- ✅ Compatible with [dart:io](https://api.dart.dev/stable/dart-io/HttpClient-class.html), [http](https://pub.dev/packages/http), and [dio](https://pub.dev/packages/dio)\n\n## Benchmark\n\nrhttp is much faster at downloading large files and a bit faster at downloading small files compared to the default HTTP client in Dart.\n\n| Small Files (1 KB)                                                                                 | Large Files (10 MB)                                                                                |\n|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|\n| ![benchmark-small](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-small.png) | ![benchmark-large](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-large.png) |\n\nReferred packages: [dio](https://pub.dev/packages/dio) (5.5.0+1), [http](https://pub.dev/packages/http) (1.2.2), [rhttp](https://pub.dev/packages/rhttp) (0.3.0)\n\nCheckout the benchmark code [here](https://github.com/Tienisto/rhttp/tree/main/benchmark).\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Request Basics](#request-basics)\n  - [HTTP methods](#-http-methods)\n  - [Request query parameters](#-request-query-parameters)\n  - [Request Headers](#-request-headers)\n  - [Request Body](#-request-body)\n  - [Response Body](#-response-body)\n- [Request Lifecycle](#request-lifecycle)\n  - [Cancel Requests](#-cancel-requests)\n  - [Progress](#-progress)\n- [Client Settings](#client-settings)\n  - [Connection Reuse](#-connection-reuse)\n  - [Keep-Alive](#-keep-alive)\n  - [Timeout](#-timeout)\n  - [Base URL](#-base-url)\n  - [HTTP version](#-http-version)\n  - [TLS version](#-tls-version)\n  - [TLS Server Name Indication (SNI)](#-tls-server-name-indication-sni)\n  - [Certificate Pinning](#-certificate-pinning)\n  - [Root certificate source](#-root-certificate-source)\n  - [Client Authentication](#-client-authentication--mutual-tls)\n  - [Disable certificate verification](#-disable-certificate-verification)\n  - [Proxy](#-proxy)\n  - [Redirects](#-redirects)\n  - [DNS resolution](#-dns-resolution)\n  - [Cookies](#-cookies)\n  - [User-Agent](#-user-agent)\n- [Intercept](#intercept)\n  - [Interceptors](#-interceptors)\n  - [RetryInterceptor](#-retryinterceptor)\n- [Error Handling](#error-handling)\n  - [Exceptions](#-exceptions)\n  - [Throw on Status Code](#-throw-on-status-code)\n- [Compatibility Layer](#compatibility-layer)\n\n## Getting Started\n\n### ➤ Installation\n\n1. Install Rust via [rustup](https://rustup.rs/).\n   - Rust 1.80.0 or later is required.\n2. For Android: Install [Command-line tools](https://stackoverflow.com/questions/68236007/i-am-getting-error-cmdline-tools-component-is-missing-after-installing-flutter)\n   - Make sure to have the latest NDK installed. [#44](https://github.com/Tienisto/rhttp/issues/44)\n3. Add `rhttp` to `pubspec.yaml`:\n\n```yaml\ndependencies:\n  rhttp: <version>\n```\n\n### ➤ Initialization\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init(); // add this\n  runApp(MyApp());\n}\n```\n\n### ➤ Usage\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  // Make a GET request\n  HttpTextResponse response = await Rhttp.get('https://example.com');\n  \n  // Read the response\n  int statusCode = response.statusCode;\n  String body = response.body;\n}\n```\n\nAlternatively, you can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package.\n\nFor more information, see [Compatibility Layer](#compatibility-layer).\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\n## Request Basics\n\n### ➤ HTTP methods\n\nYou can make requests using different HTTP methods:\n\n```dart\n// Pass the method as an argument\nawait Rhttp.requestText(method: HttpMethod.post, url: 'https://example.com');\n\n// Use the helper methods\nawait Rhttp.post('https://example.com');\n```\n\n### ➤ Request query parameters\n\nYou can add query parameters to the URL:\n\n```dart\nawait Rhttp.get('https://example.com', query: {'key': 'value'});\n```\n\n### ➤ Request Headers\n\nYou can add headers to the request:\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  headers: const HttpHeaders.map({\n    HttpHeaderName.contentType: 'application/json',\n  }),\n);\n```\n\n### ➤ Request Body\n\nYou can add a body to the request. There are different types of bodies you can use:\n\n**Text**\n\nPass a string to the `HttpBody.text` constructor.\n\n```dart\n// Raw body\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.text('raw body'),\n);\n```\n\n**JSON**\n\nPass a JSON data structure to the `HttpBody.json` constructor.\n\nThe Content-Type header will be set to `application/json` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.json({'key': 'value'}),\n);\n```\n\n**Binary**\n\nPass a `Uint8List` to the `HttpBody.bytes` constructor.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(Uint8List.fromList([0, 1, 2])),\n);\n```\n\n**Stream**\n\nPass a `Stream<List<int>>` to the `HttpBody.stream` constructor.\n\nIt is recommended to also provide a `length` to automatically set the `Content-Length` header.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.stream(\n    Stream.fromIterable([[1, 2, 3]]),\n    length: 3,\n  ),\n);\n```\n\n**Form**\n\nPass a flat map to the `HttpBody.form` constructor.\n\nThe Content-Type header will be set to `application/x-www-form-urlencoded` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.form({'key': 'value'}),\n);\n```\n\n**Multipart**\n\nPass a map of `MultipartItem` to the `HttpBody.multipart` constructor.\n\nThe Content-Type header will be overridden to `multipart/form-data` with a random boundary.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.multipart({\n    'name': const MultipartItem.text(\n      text: 'Tom',\n    ),\n    'profile_image': MultipartItem.bytes(\n      bytes: Uint8List.fromList(bytes),\n      fileName: 'image.jpeg',\n    ),\n  }),\n)\n```\n\n### ➤ Response Body\n\nTo let Rust do most of the work, you must specify the expected response body type before making the request.\n\n```dart\nHttpTextResponse response = await Rhttp.getText('https://example.com');\nString body = response.body;\n\nHttpBytesResponse response = await Rhttp.getBytes('https://example.com');\nUint8List body = response.body;\n\nHttpStreamResponse response = await Rhttp.getStream('https://example.com');\nStream<Uint8List> body = response.body;\n```\n\nThey all extend the `HttpResponse` class, which contains the following properties:\n\n| Property                                  | Description                                                     |\n|-------------------------------------------|-----------------------------------------------------------------|\n| `String? remoteIp`                        | The remote IP address of the server that sent the response.     |\n| `HttpRequest request`                     | The HTTP request that this response is associated with.         |\n| `HttpVersion version`                     | The HTTP version of this response.                              |\n| `int statusCode`                          | The HTTP status code of this response.                          |\n| `List<(String, String)> headers`          | The HTTP headers of this response.                              |\n| `Map<String, String> headerMap`           | Response headers converted as a map.                            |\n| `Map<String, List<String>> headerMapList` | Response headers converted as a map respecting multiple values. |\n\n## Request Lifecycle\n\n### ➤ Cancel Requests\n\nYou can cancel a request by providing a `CancelToken`.\n\nIf the same `CancelToken` is used for multiple requests, all requests will be canceled.\n\nIf a canceled `CancelToken` is used for a request, the request will be canceled immediately.\n\n```dart\nfinal cancelToken = CancelToken();\nfinal request = Rhttp.get(\n   'https://example.com',\n   cancelToken: cancelToken,\n);\n\n// Cancel the request\ncancelToken.cancel();\n\n// Will throw a `RhttpCancelException`\nawait request;\n```\n\n### ➤ Progress\n\nYou can observe the progress of the request, by providing `onSendProgress` and `onReceiveProgress` callbacks.\n\nPlease note that request and response bodies must be either `Stream` or `Uint8List`.\n\nThe parameter `total` can be `-1` if the total size is unknown.\n\nIt always emits the final value with `sent` / `received` and `total` being equal after the request is finished.\n\n```dart\nfinal request = Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(bytes),\n  onSendProgress: (sent, total) {\n    print('Sent: $sent, Total: $total');\n  },\n  onReceiveProgress: (received, total) {\n    print('Received: $received, Total: $total');\n  },\n);\n```\n\n## Client Settings\n\n### ➤ Connection Reuse\n\nTo improve performance, it is recommended to create a client and reuse it for multiple requests.\n\nThis allows you to reuse connections (with same servers).\nFurthermore, it avoids the overhead of creating a new client for each request.\n\n```dart\nfinal client = await RhttpClient.create();\n\nawait client.get('https://example.com');\n```\n\nYou can dispose the client when you are done with it:\n\n```dart\nclient.dispose();\n```\n\nTo create a client synchronously, use `RhttpClient.createSync`.\nThis should only be called during app start to avoid blocking the UI thread.\n\n```dart\nfinal client = RhttpClient.createSync();\n```\n\n### ➤ Keep-Alive\n\nBy default, connections are not kept alive. On HTTP/2, the same connection\nis reused for multiple requests that are done on the same time, but the socket\nis closed immediately after the last request is finished.\n\nSetting `keepAliveTimeout` to a value greater than `0` will keep the socket \nopen when idle for the specified duration, both in HTTP/1.1 and HTTP/2.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      keepAliveTimeout: Duration(seconds: 60),\n      keepAlivePing: Duration(seconds: 30),\n    ),\n  ),\n);\n```\n\n### ➤ Timeout\n\nYou can specify the timeout for the request:\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      timeout: Duration(seconds: 10),\n      connectTimeout: Duration(seconds: 5),\n    ),\n  ),\n);\n```\n\n### ➤ Base URL\n\nAdd a base URL to the client to avoid repeating the same URL or to change the base URL easily.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    baseUrl: 'https://example.com',\n  ),\n);\n```\n\n### ➤ HTTP version\n\nYou can specify the HTTP version to use for the request.\nHTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    httpVersionPref: HttpVersionPref.http3,\n  ),\n);\n```\n\n### ➤ TLS version\n\nYou can specify the TLS version to use for the request.\nOnly TLS 1.2 and 1.3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      minTlsVersion: TlsVersion.tls12,\n      maxTlsVersion: TlsVersion.tls13,\n    ),\n  ),\n);\n```\n\n### ➤ TLS Server Name Indication (SNI)\n\nControls the use of TLS server name indication.\n\nThis option is enabled by default.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      sni: false,\n    ),\n  ),\n);\n```\n\n### ➤ Certificate Pinning\n\nTo improve security, you can specify the expected server certificate.\n\nDue to limitations on Rust's side ([Github Issue](https://github.com/seanmonstar/reqwest/issues/298)),\nyou need to either provide the full certificate chain, or the root certificate.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      trustedRootCertificates: [\n        '''-----BEGIN CERTIFICATE-----\nsome certificate\n-----END CERTIFICATE-----''',\n],\n    ),\n  ),\n);\n```\n\n### ➤ Root certificate source\n\nBy default, the root certificates provided by Mozilla (webpki) are used.\nAs of now, this is the most reliable option which requires no additional setup.\n\nYou can configure which root certificates are trusted by setting `TlsSettings.rootCertSource`.\n\n| Mode       | Description                                             | Notes                                                                                                   |\n|------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------|\n| `platform` | Use root certificates provided by the operating system. | Flexible, but may be inconsistent across platforms.                                                     |\n| `webpki`   | Use root certificates provided by Mozilla. (default)    | Consistent across platforms, but requires manual app updates. Root certs are valid for around 15 years. |\n| `none`     | Don't trust any root certificates.                      | Special use cases                                                                                       |\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      rootCertSource: RootCertSource.none,\n    ),\n  ),\n);\n```\n\n#### Android Proguard Exclusions (only required for `RootCertSource.platform`)\n\nThe `platform` mode relies on [`rustls-platform-verifier`](https://github.com/rustls/rustls-platform-verifier#proguard),\nwhose Java classes are stripped by Android's [R8 shrinker](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization)\nin release builds. The `webpki` (default) and `none` modes do not use these classes and need no Proguard configuration.\n\nIf you use `RootCertSource.platform`, add a `android/app/proguard-rules.pro` file if it does not already exist\nand add or append the following line:\n```\n-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; }\n```\nIf the proguard file did not exist earlier it must also be added to `android/app/build.gradle.kts`:\n```diff\n android {\n    ...\n    buildTypes {\n        release {\n            ...\n   \n+            proguardFiles(\n+                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n+                \"proguard-rules.pro\"\n             )\n         }\n     }\n}\n```\n\n### ➤ Client Authentication / mutual TLS\n\nYou can specify the client certificate and key to enable mutual TLS (mTLS).\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      clientCertificate: ClientCertificate(\n         certificate: clientCert,\n         privateKey: clientKey,\n      ),\n    ),\n  ),\n);\n```\n\n### ➤ Disable certificate verification\n\nThis is very insecure and should only be used for testing purposes.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      verifyCertificates: false,\n    ),\n  ),\n);\n```\n\n### ➤ Proxy\n\nBy default, the system proxy is enabled.\n\nDisable system proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.noProxy(),\n  ),\n);\n```\n\nUse a custom proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.proxy('http://localhost:8080'),\n  ),\n);\n```\n\nOnly proxy unencrypted HTTP traffic:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.static(\n      url: 'http://localhost:8080',\n      condition: ProxyCondition.onlyHttp,\n    ),\n  ),\n);\n```\n\nChain multiple proxies:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.list([\n      StaticProxy(\n        url: 'http://localhost:8080',\n        condition: ProxyCondition.onlyHttp,\n      ),\n      StaticProxy(\n        url: 'http://localhost:8081',\n        condition: ProxyCondition.onlyHttps,\n      ),\n    ]),\n  ),\n);\n```\n\n### ➤ Redirects\n\nBy default, up to 10 redirects (e.g. HTTP 302) are followed.\n\nExceeding the maximum number of redirects will throw a `RhttpRedirectException`.\n\nYou can change the maximum number of redirects and whether to follow redirects:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    redirectSettings: RedirectSettings.limited(5), // or RedirectSettings.none()\n  ),\n);\n```\n\n### ➤ DNS resolution\n\nBy default, the system DNS resolver is used.\n\nYou can override the mapping of hostnames to IP addresses:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1'],\n      },\n    ),\n  )\n);\n```\n\nFor a more complex DNS resolution, you can construct a `DnsSettings.dynamic` object:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: ClientSettings(\n    dnsSettings: DnsSettings.dynamic(\n      resolver: (String host) async {\n        if (counter % 2 == 0) {\n          return ['127.0.0.1'];\n        } else {\n          return ['1.2.3.4'];\n        }\n      }\n    ),\n  )\n);\n```\n\nBy default, the conventional port is used. You can override this behaviour by specifying the port:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1:8080'],\n      },\n    ),\n  )\n);\n```\n\n### ➤ Cookies\n\nIt is possible to optionally activate automatic Cookie handling. This will store Cookies sent by the\nserver in an ephemeral Cookie [`Jar`](https://docs.rs/reqwest/latest/reqwest/cookie/struct.Jar.html).\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    cookieSettings: CookieSettings(storeCookies: true),\n  ),\n);\n```\n\n### ➤ User-Agent\n\nA convenient way to set the `User-Agent` header.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    userAgent: 'MyApp/1.0',\n  ),\n);\n```\n\n## Intercept\n\n### ➤ Interceptors\n\nYou can add interceptors to the client to modify requests / responses, handle errors, observe requests, etc.\n\nAny exception thrown by an interceptor that is not a subclass of `RhttpException`\nwill be caught and wrapped in a `RhttpInterceptorException`.\n\n```dart\nclass TestInterceptor extends Interceptor {\n  @override\n  Future<InterceptorResult<HttpRequest>> beforeRequest(\n    HttpRequest request,\n  ) async {\n    return Interceptor.next(request.addHeader(\n      name: HttpHeaderName.accept,\n      value: 'application/json',\n    ));\n  }\n\n  @override\n  Future<InterceptorResult<HttpResponse>> afterResponse(\n    HttpResponse response,\n  ) async {\n    return Interceptor.next();\n  }\n\n  @override\n  Future<InterceptorResult<RhttpException>> onError(\n    RhttpException exception,\n  ) async {\n    return Interceptor.next();\n  }\n}\n```\n\nThere are 4 termination methods:\n\n- `Interceptor.next()`: Continue with the next interceptor.\n- `Interceptor.stop()`: Stop the interceptor chain.\n- `Interceptor.resolve()`: Resolve the request with the given response.\n- `throw RhttpException`: Throw an exception. The stack trace will be preserved.\n\nInstead of implementing the `Interceptor` class, you can use the `SimpleInterceptor` class:\n\n```dart\nfinal client = await RhttpClient.create(\n  interceptors: [\n    SimpleInterceptor(\n      onError: (exception) async {\n        if (exception is RhttpStatusCodeException && exception.statusCode == 401) {\n          // Log out\n        }\n        return Interceptor.next();\n      },\n    ),\n  ],\n);\n```\n\n### ➤ RetryInterceptor\n\nThere is a built-in `RetryInterceptor` that retries the request if it fails.\n\nDuring the retry, all interceptors except `RetryInterceptor` are called again.\n\n```dart\nclass RefreshTokenInterceptor extends RetryInterceptor {\n  final Ref ref;\n\n  RefreshTokenInterceptor(this.ref);\n\n  @override\n  int get maxRetries => 1;\n\n  @override\n  bool shouldRetry(HttpResponse? response, RhttpException? exception) {\n    return exception is RhttpStatusCodeException &&\n        (exception.statusCode == 401 || exception.statusCode == 403);\n  }\n\n  @override\n  Future<HttpRequest?> beforeRetry(\n    int attempt,\n    HttpRequest request,\n    HttpResponse? response,\n    RhttpException? exception,\n  ) async {\n    ref.read(authProvider.notifier).state = await refresh();\n    return null;\n  }\n}\n```\n\nCheckout this [example](https://github.com/Tienisto/rhttp/blob/main/rhttp/example/lib/interceptor_riverpod.dart)\nto see how access tokens can be refreshed using Riverpod.\n\n## Error Handling\n\n### ➤ Exceptions\n\nAll exceptions are subclasses of `RhttpException`.\n\nThe following exceptions can be thrown:\n\n| Exception                          | Description                                           |\n|------------------------------------|-------------------------------------------------------|\n| `RhttpCancelException`             | Request was canceled.                                 |\n| `RhttpTimeoutException`            | Request timed out.                                    |\n| `RhttpRedirectException`           | Too many redirects.                                   |\n| `RhttpStatusCodeException`         | Response has 4xx or 5xx status code.                  |\n| `RhttpInvalidCertificateException` | Server certificate is invalid.                        |\n| `RhttpConnectionException`         | Connection error. (no internet, server not reachable) |\n| `RhttpClientDisposedException`     | Client is already disposed.                           |\n| `RhttpInterceptorException`        | Interceptor threw an exception.                       |\n| `RhttpUnknownException`            | Unknown error occurred.                               |\n\n### ➤ Throw on Status Code\n\nBy default, an exception is thrown if the response has a 4xx or 5xx status code.\nYou can disable this behavior by setting `throwOnStatusCode` to `false`.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    throwOnStatusCode: false,\n  ),\n);\n```\n\n## Compatibility Layer\n\nYou can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package,\nthereby exposing the same API as the default HTTP client in the Dart ecosystem.\n\nThis comes with some downsides, such as:\n\n- inferior type safety due to the flaw that `body` is of type `Object?` instead of an explicit type\n- body of type `Map` is implicitly interpreted as `x-www-form-urlencoded` that cannot be changed\n- no support for cancellation\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\nBecause this client is compatible with [http](https://pub.dev/packages/http),\nyou can use [dio_compatibility_layer](https://pub.dev/packages/dio_compatibility_layer)\nto use rhttp with the [dio](https://pub.dev/packages/dio) package.\n\n```dart\nFuture<Dio> createDioClient() async {\n  final dio = Dio();\n  final compatibleClient = await RhttpCompatibleClient.create(); // or createSync()\n  dio.httpClientAdapter = ConversionLayerAdapter(compatibleClient);\n  return dio;\n}\n```\n\nIf you are looking for a replacement for `HttpClient` of `dart:io`, you can use the `IoCompatibleClient`:\n\n```dart\nimport 'dart:io';\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  final client = await IoCompatibleClient.create();\n  final request = await client.getUrl(Uri.parse('https://example.com'));\n  final response = await request.close();\n\n  print(response.statusCode);\n  print(await response.transform(utf8.decoder).join());\n}\n```\n\n## License\n\nMIT License\n\nCopyright (c) 2024-2026 Tien Do Nam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","plugins/rhttp/rhttp/cargokit/build_tool/README.md":"/// This is copied from Cargokit (which is the official way to use it currently)\n/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin\n\nA sample command-line application with an entrypoint in `bin/`, library code\nin `lib/`, and example unit test in `test/`.\n","plugins/rhttp/rhttp/example/README.md":"# rhttp_example\n\nDemonstrates how to use the rhttp plugin.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)\n\nFor help getting started with Flutter development, view the\n[online documentation](https://docs.flutter.dev/), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n","plugins/rhttp/rhttp/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md":"# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images."},"items":[{"name":"README.md","path":"plugins/rhttp/benchmark/README.md","rawUrl":"https://raw.githubusercontent.com/Notsfsssf/pixez-flutter/HEAD/plugins/rhttp/benchmark/README.md","title":"benchmark Documentation","category":"plugin-manifest","format":"markdown","content":"# benchmark\n\nStart server:\n\n```shell\ncd nodejs\nnode server.js\n```\n\nStart benchmark:\n\n```shell\ncd benchmark\nflutter run --release\n```\n\n## 1 KB x 10000\n- rhttp: 1010 ms\n- http: 2174 ms\n- dio: 2758 ms\n\n## 10 MB x 100\n- rhttp: 2394 ms\n- http: 12527 ms\n- dio: 13091 ms\n","isInternal":false,"tokens":66,"sizeBytes":264},{"name":"README.md","path":"plugins/rhttp/README.md","rawUrl":"https://raw.githubusercontent.com/Notsfsssf/pixez-flutter/HEAD/plugins/rhttp/README.md","title":"rhttp Documentation","category":"plugin-manifest","format":"markdown","content":"rhttp/README.md","isInternal":false,"tokens":4,"sizeBytes":15},{"name":"README.md","path":"plugins/rhttp/rhttp/cargokit/build_tool/README.md","rawUrl":"https://raw.githubusercontent.com/Notsfsssf/pixez-flutter/HEAD/plugins/rhttp/rhttp/cargokit/build_tool/README.md","title":"build_tool Documentation","category":"plugin-manifest","format":"markdown","content":"/// This is copied from Cargokit (which is the official way to use it currently)\n/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin\n\nA sample command-line application with an entrypoint in `bin/`, library code\nin `lib/`, and example unit test in `test/`.\n","isInternal":false,"tokens":72,"sizeBytes":288},{"name":"README.md","path":"plugins/rhttp/rhttp/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md","rawUrl":"https://raw.githubusercontent.com/Notsfsssf/pixez-flutter/HEAD/plugins/rhttp/rhttp/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md","title":"LaunchImage.imageset Documentation","category":"plugin-manifest","format":"markdown","content":"# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.","isInternal":false,"tokens":84,"sizeBytes":336},{"name":"README.md","path":"plugins/rhttp/rhttp/example/README.md","rawUrl":"https://raw.githubusercontent.com/Notsfsssf/pixez-flutter/HEAD/plugins/rhttp/rhttp/example/README.md","title":"example Documentation","category":"plugin-manifest","format":"markdown","content":"# rhttp_example\n\nDemonstrates how to use the rhttp plugin.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)\n\nFor help getting started with Flutter development, view the\n[online documentation](https://docs.flutter.dev/), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n","isInternal":false,"tokens":144,"sizeBytes":575},{"name":"README.md","path":"plugins/rhttp/rhttp/README.md","rawUrl":"https://raw.githubusercontent.com/Notsfsssf/pixez-flutter/HEAD/plugins/rhttp/rhttp/README.md","title":"rhttp Documentation","category":"plugin-manifest","format":"markdown","content":"# rhttp\n\n[![pub package](https://img.shields.io/pub/v/rhttp.svg)](https://pub.dev/packages/rhttp)\n![ci](https://codeberg.org/Tienisto/rhttp/actions/workflows/ci.yml/badge.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nMake HTTP requests using Rust for Flutter developers.\n\n## About\n\nThis package is a Dart wrapper around the [reqwest](https://crates.io/crates/reqwest) crate, which is a fast and reliable HTTP client for Rust.\nFor optimal performance, we use FFI with [flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge) to call Rust code.\n\nThe default HTTP client in Dart is part of `dart:io`, which lacks configurability and performance compared to other HTTP clients.\nFurthermore, HTTP/2 and HTTP/3 are either missing or not supported by default.\n\nCompared to [cronet_http](https://pub.dev/packages/cronet_http) and [cupertino_http](https://pub.dev/packages/cupertino_http), this package offers a unified, feature-rich API\nthat also works on Windows and Linux.\n\nThe APK size will increase by 2 MB on arm64 and 6 MB if compiled for all architectures (x64, arm32, arm64).\n\nWeb is currently not supported.\n\n## Features\n\n- ✅ HTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 support\n- ✅ TLS 1.2 and 1.3 support\n- ✅ Connection pooling\n- ✅ Interceptors\n- ✅ Retry (optional)\n- ✅ Certificate pinning\n- ✅ Proxy support\n- ✅ Custom DNS resolution\n- ✅ Cookies\n- ✅ Strong type safety\n- ✅ DevTools support ([Network tab](https://docs.flutter.dev/tools/devtools/network))\n- ✅ Compatible with [dart:io](https://api.dart.dev/stable/dart-io/HttpClient-class.html), [http](https://pub.dev/packages/http), and [dio](https://pub.dev/packages/dio)\n\n## Benchmark\n\nrhttp is much faster at downloading large files and a bit faster at downloading small files compared to the default HTTP client in Dart.\n\n| Small Files (1 KB)                                                                                 | Large Files (10 MB)                                                                                |\n|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|\n| ![benchmark-small](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-small.png) | ![benchmark-large](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-large.png) |\n\nReferred packages: [dio](https://pub.dev/packages/dio) (5.5.0+1), [http](https://pub.dev/packages/http) (1.2.2), [rhttp](https://pub.dev/packages/rhttp) (0.3.0)\n\nCheckout the benchmark code [here](https://github.com/Tienisto/rhttp/tree/main/benchmark).\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Request Basics](#request-basics)\n  - [HTTP methods](#-http-methods)\n  - [Request query parameters](#-request-query-parameters)\n  - [Request Headers](#-request-headers)\n  - [Request Body](#-request-body)\n  - [Response Body](#-response-body)\n- [Request Lifecycle](#request-lifecycle)\n  - [Cancel Requests](#-cancel-requests)\n  - [Progress](#-progress)\n- [Client Settings](#client-settings)\n  - [Connection Reuse](#-connection-reuse)\n  - [Keep-Alive](#-keep-alive)\n  - [Timeout](#-timeout)\n  - [Base URL](#-base-url)\n  - [HTTP version](#-http-version)\n  - [TLS version](#-tls-version)\n  - [TLS Server Name Indication (SNI)](#-tls-server-name-indication-sni)\n  - [Certificate Pinning](#-certificate-pinning)\n  - [Root certificate source](#-root-certificate-source)\n  - [Client Authentication](#-client-authentication--mutual-tls)\n  - [Disable certificate verification](#-disable-certificate-verification)\n  - [Proxy](#-proxy)\n  - [Redirects](#-redirects)\n  - [DNS resolution](#-dns-resolution)\n  - [Cookies](#-cookies)\n  - [User-Agent](#-user-agent)\n- [Intercept](#intercept)\n  - [Interceptors](#-interceptors)\n  - [RetryInterceptor](#-retryinterceptor)\n- [Error Handling](#error-handling)\n  - [Exceptions](#-exceptions)\n  - [Throw on Status Code](#-throw-on-status-code)\n- [Compatibility Layer](#compatibility-layer)\n\n## Getting Started\n\n### ➤ Installation\n\n1. Install Rust via [rustup](https://rustup.rs/).\n   - Rust 1.80.0 or later is required.\n2. For Android: Install [Command-line tools](https://stackoverflow.com/questions/68236007/i-am-getting-error-cmdline-tools-component-is-missing-after-installing-flutter)\n   - Make sure to have the latest NDK installed. [#44](https://github.com/Tienisto/rhttp/issues/44)\n3. Add `rhttp` to `pubspec.yaml`:\n\n```yaml\ndependencies:\n  rhttp: <version>\n```\n\n### ➤ Initialization\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init(); // add this\n  runApp(MyApp());\n}\n```\n\n### ➤ Usage\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  // Make a GET request\n  HttpTextResponse response = await Rhttp.get('https://example.com');\n  \n  // Read the response\n  int statusCode = response.statusCode;\n  String body = response.body;\n}\n```\n\nAlternatively, you can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package.\n\nFor more information, see [Compatibility Layer](#compatibility-layer).\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\n## Request Basics\n\n### ➤ HTTP methods\n\nYou can make requests using different HTTP methods:\n\n```dart\n// Pass the method as an argument\nawait Rhttp.requestText(method: HttpMethod.post, url: 'https://example.com');\n\n// Use the helper methods\nawait Rhttp.post('https://example.com');\n```\n\n### ➤ Request query parameters\n\nYou can add query parameters to the URL:\n\n```dart\nawait Rhttp.get('https://example.com', query: {'key': 'value'});\n```\n\n### ➤ Request Headers\n\nYou can add headers to the request:\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  headers: const HttpHeaders.map({\n    HttpHeaderName.contentType: 'application/json',\n  }),\n);\n```\n\n### ➤ Request Body\n\nYou can add a body to the request. There are different types of bodies you can use:\n\n**Text**\n\nPass a string to the `HttpBody.text` constructor.\n\n```dart\n// Raw body\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.text('raw body'),\n);\n```\n\n**JSON**\n\nPass a JSON data structure to the `HttpBody.json` constructor.\n\nThe Content-Type header will be set to `application/json` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.json({'key': 'value'}),\n);\n```\n\n**Binary**\n\nPass a `Uint8List` to the `HttpBody.bytes` constructor.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(Uint8List.fromList([0, 1, 2])),\n);\n```\n\n**Stream**\n\nPass a `Stream<List<int>>` to the `HttpBody.stream` constructor.\n\nIt is recommended to also provide a `length` to automatically set the `Content-Length` header.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.stream(\n    Stream.fromIterable([[1, 2, 3]]),\n    length: 3,\n  ),\n);\n```\n\n**Form**\n\nPass a flat map to the `HttpBody.form` constructor.\n\nThe Content-Type header will be set to `application/x-www-form-urlencoded` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.form({'key': 'value'}),\n);\n```\n\n**Multipart**\n\nPass a map of `MultipartItem` to the `HttpBody.multipart` constructor.\n\nThe Content-Type header will be overridden to `multipart/form-data` with a random boundary.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.multipart({\n    'name': const MultipartItem.text(\n      text: 'Tom',\n    ),\n    'profile_image': MultipartItem.bytes(\n      bytes: Uint8List.fromList(bytes),\n      fileName: 'image.jpeg',\n    ),\n  }),\n)\n```\n\n### ➤ Response Body\n\nTo let Rust do most of the work, you must specify the expected response body type before making the request.\n\n```dart\nHttpTextResponse response = await Rhttp.getText('https://example.com');\nString body = response.body;\n\nHttpBytesResponse response = await Rhttp.getBytes('https://example.com');\nUint8List body = response.body;\n\nHttpStreamResponse response = await Rhttp.getStream('https://example.com');\nStream<Uint8List> body = response.body;\n```\n\nThey all extend the `HttpResponse` class, which contains the following properties:\n\n| Property                                  | Description                                                     |\n|-------------------------------------------|-----------------------------------------------------------------|\n| `String? remoteIp`                        | The remote IP address of the server that sent the response.     |\n| `HttpRequest request`                     | The HTTP request that this response is associated with.         |\n| `HttpVersion version`                     | The HTTP version of this response.                              |\n| `int statusCode`                          | The HTTP status code of this response.                          |\n| `List<(String, String)> headers`          | The HTTP headers of this response.                              |\n| `Map<String, String> headerMap`           | Response headers converted as a map.                            |\n| `Map<String, List<String>> headerMapList` | Response headers converted as a map respecting multiple values. |\n\n## Request Lifecycle\n\n### ➤ Cancel Requests\n\nYou can cancel a request by providing a `CancelToken`.\n\nIf the same `CancelToken` is used for multiple requests, all requests will be canceled.\n\nIf a canceled `CancelToken` is used for a request, the request will be canceled immediately.\n\n```dart\nfinal cancelToken = CancelToken();\nfinal request = Rhttp.get(\n   'https://example.com',\n   cancelToken: cancelToken,\n);\n\n// Cancel the request\ncancelToken.cancel();\n\n// Will throw a `RhttpCancelException`\nawait request;\n```\n\n### ➤ Progress\n\nYou can observe the progress of the request, by providing `onSendProgress` and `onReceiveProgress` callbacks.\n\nPlease note that request and response bodies must be either `Stream` or `Uint8List`.\n\nThe parameter `total` can be `-1` if the total size is unknown.\n\nIt always emits the final value with `sent` / `received` and `total` being equal after the request is finished.\n\n```dart\nfinal request = Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(bytes),\n  onSendProgress: (sent, total) {\n    print('Sent: $sent, Total: $total');\n  },\n  onReceiveProgress: (received, total) {\n    print('Received: $received, Total: $total');\n  },\n);\n```\n\n## Client Settings\n\n### ➤ Connection Reuse\n\nTo improve performance, it is recommended to create a client and reuse it for multiple requests.\n\nThis allows you to reuse connections (with same servers).\nFurthermore, it avoids the overhead of creating a new client for each request.\n\n```dart\nfinal client = await RhttpClient.create();\n\nawait client.get('https://example.com');\n```\n\nYou can dispose the client when you are done with it:\n\n```dart\nclient.dispose();\n```\n\nTo create a client synchronously, use `RhttpClient.createSync`.\nThis should only be called during app start to avoid blocking the UI thread.\n\n```dart\nfinal client = RhttpClient.createSync();\n```\n\n### ➤ Keep-Alive\n\nBy default, connections are not kept alive. On HTTP/2, the same connection\nis reused for multiple requests that are done on the same time, but the socket\nis closed immediately after the last request is finished.\n\nSetting `keepAliveTimeout` to a value greater than `0` will keep the socket \nopen when idle for the specified duration, both in HTTP/1.1 and HTTP/2.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      keepAliveTimeout: Duration(seconds: 60),\n      keepAlivePing: Duration(seconds: 30),\n    ),\n  ),\n);\n```\n\n### ➤ Timeout\n\nYou can specify the timeout for the request:\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      timeout: Duration(seconds: 10),\n      connectTimeout: Duration(seconds: 5),\n    ),\n  ),\n);\n```\n\n### ➤ Base URL\n\nAdd a base URL to the client to avoid repeating the same URL or to change the base URL easily.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    baseUrl: 'https://example.com',\n  ),\n);\n```\n\n### ➤ HTTP version\n\nYou can specify the HTTP version to use for the request.\nHTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    httpVersionPref: HttpVersionPref.http3,\n  ),\n);\n```\n\n### ➤ TLS version\n\nYou can specify the TLS version to use for the request.\nOnly TLS 1.2 and 1.3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      minTlsVersion: TlsVersion.tls12,\n      maxTlsVersion: TlsVersion.tls13,\n    ),\n  ),\n);\n```\n\n### ➤ TLS Server Name Indication (SNI)\n\nControls the use of TLS server name indication.\n\nThis option is enabled by default.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      sni: false,\n    ),\n  ),\n);\n```\n\n### ➤ Certificate Pinning\n\nTo improve security, you can specify the expected server certificate.\n\nDue to limitations on Rust's side ([Github Issue](https://github.com/seanmonstar/reqwest/issues/298)),\nyou need to either provide the full certificate chain, or the root certificate.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      trustedRootCertificates: [\n        '''-----BEGIN CERTIFICATE-----\nsome certificate\n-----END CERTIFICATE-----''',\n],\n    ),\n  ),\n);\n```\n\n### ➤ Root certificate source\n\nBy default, the root certificates provided by Mozilla (webpki) are used.\nAs of now, this is the most reliable option which requires no additional setup.\n\nYou can configure which root certificates are trusted by setting `TlsSettings.rootCertSource`.\n\n| Mode       | Description                                             | Notes                                                                                                   |\n|------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------|\n| `platform` | Use root certificates provided by the operating system. | Flexible, but may be inconsistent across platforms.                                                     |\n| `webpki`   | Use root certificates provided by Mozilla. (default)    | Consistent across platforms, but requires manual app updates. Root certs are valid for around 15 years. |\n| `none`     | Don't trust any root certificates.                      | Special use cases                                                                                       |\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      rootCertSource: RootCertSource.none,\n    ),\n  ),\n);\n```\n\n#### Android Proguard Exclusions (only required for `RootCertSource.platform`)\n\nThe `platform` mode relies on [`rustls-platform-verifier`](https://github.com/rustls/rustls-platform-verifier#proguard),\nwhose Java classes are stripped by Android's [R8 shrinker](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization)\nin release builds. The `webpki` (default) and `none` modes do not use these classes and need no Proguard configuration.\n\nIf you use `RootCertSource.platform`, add a `android/app/proguard-rules.pro` file if it does not already exist\nand add or append the following line:\n```\n-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; }\n```\nIf the proguard file did not exist earlier it must also be added to `android/app/build.gradle.kts`:\n```diff\n android {\n    ...\n    buildTypes {\n        release {\n            ...\n   \n+            proguardFiles(\n+                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n+                \"proguard-rules.pro\"\n             )\n         }\n     }\n}\n```\n\n### ➤ Client Authentication / mutual TLS\n\nYou can specify the client certificate and key to enable mutual TLS (mTLS).\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      clientCertificate: ClientCertificate(\n         certificate: clientCert,\n         privateKey: clientKey,\n      ),\n    ),\n  ),\n);\n```\n\n### ➤ Disable certificate verification\n\nThis is very insecure and should only be used for testing purposes.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      verifyCertificates: false,\n    ),\n  ),\n);\n```\n\n### ➤ Proxy\n\nBy default, the system proxy is enabled.\n\nDisable system proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.noProxy(),\n  ),\n);\n```\n\nUse a custom proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.proxy('http://localhost:8080'),\n  ),\n);\n```\n\nOnly proxy unencrypted HTTP traffic:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.static(\n      url: 'http://localhost:8080',\n      condition: ProxyCondition.onlyHttp,\n    ),\n  ),\n);\n```\n\nChain multiple proxies:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.list([\n      StaticProxy(\n        url: 'http://localhost:8080',\n        condition: ProxyCondition.onlyHttp,\n      ),\n      StaticProxy(\n        url: 'http://localhost:8081',\n        condition: ProxyCondition.onlyHttps,\n      ),\n    ]),\n  ),\n);\n```\n\n### ➤ Redirects\n\nBy default, up to 10 redirects (e.g. HTTP 302) are followed.\n\nExceeding the maximum number of redirects will throw a `RhttpRedirectException`.\n\nYou can change the maximum number of redirects and whether to follow redirects:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    redirectSettings: RedirectSettings.limited(5), // or RedirectSettings.none()\n  ),\n);\n```\n\n### ➤ DNS resolution\n\nBy default, the system DNS resolver is used.\n\nYou can override the mapping of hostnames to IP addresses:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1'],\n      },\n    ),\n  )\n);\n```\n\nFor a more complex DNS resolution, you can construct a `DnsSettings.dynamic` object:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: ClientSettings(\n    dnsSettings: DnsSettings.dynamic(\n      resolver: (String host) async {\n        if (counter % 2 == 0) {\n          return ['127.0.0.1'];\n        } else {\n          return ['1.2.3.4'];\n        }\n      }\n    ),\n  )\n);\n```\n\nBy default, the conventional port is used. You can override this behaviour by specifying the port:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1:8080'],\n      },\n    ),\n  )\n);\n```\n\n### ➤ Cookies\n\nIt is possible to optionally activate automatic Cookie handling. This will store Cookies sent by the\nserver in an ephemeral Cookie [`Jar`](https://docs.rs/reqwest/latest/reqwest/cookie/struct.Jar.html).\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    cookieSettings: CookieSettings(storeCookies: true),\n  ),\n);\n```\n\n### ➤ User-Agent\n\nA convenient way to set the `User-Agent` header.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    userAgent: 'MyApp/1.0',\n  ),\n);\n```\n\n## Intercept\n\n### ➤ Interceptors\n\nYou can add interceptors to the client to modify requests / responses, handle errors, observe requests, etc.\n\nAny exception thrown by an interceptor that is not a subclass of `RhttpException`\nwill be caught and wrapped in a `RhttpInterceptorException`.\n\n```dart\nclass TestInterceptor extends Interceptor {\n  @override\n  Future<InterceptorResult<HttpRequest>> beforeRequest(\n    HttpRequest request,\n  ) async {\n    return Interceptor.next(request.addHeader(\n      name: HttpHeaderName.accept,\n      value: 'application/json',\n    ));\n  }\n\n  @override\n  Future<InterceptorResult<HttpResponse>> afterResponse(\n    HttpResponse response,\n  ) async {\n    return Interceptor.next();\n  }\n\n  @override\n  Future<InterceptorResult<RhttpException>> onError(\n    RhttpException exception,\n  ) async {\n    return Interceptor.next();\n  }\n}\n```\n\nThere are 4 termination methods:\n\n- `Interceptor.next()`: Continue with the next interceptor.\n- `Interceptor.stop()`: Stop the interceptor chain.\n- `Interceptor.resolve()`: Resolve the request with the given response.\n- `throw RhttpException`: Throw an exception. The stack trace will be preserved.\n\nInstead of implementing the `Interceptor` class, you can use the `SimpleInterceptor` class:\n\n```dart\nfinal client = await RhttpClient.create(\n  interceptors: [\n    SimpleInterceptor(\n      onError: (exception) async {\n        if (exception is RhttpStatusCodeException && exception.statusCode == 401) {\n          // Log out\n        }\n        return Interceptor.next();\n      },\n    ),\n  ],\n);\n```\n\n### ➤ RetryInterceptor\n\nThere is a built-in `RetryInterceptor` that retries the request if it fails.\n\nDuring the retry, all interceptors except `RetryInterceptor` are called again.\n\n```dart\nclass RefreshTokenInterceptor extends RetryInterceptor {\n  final Ref ref;\n\n  RefreshTokenInterceptor(this.ref);\n\n  @override\n  int get maxRetries => 1;\n\n  @override\n  bool shouldRetry(HttpResponse? response, RhttpException? exception) {\n    return exception is RhttpStatusCodeException &&\n        (exception.statusCode == 401 || exception.statusCode == 403);\n  }\n\n  @override\n  Future<HttpRequest?> beforeRetry(\n    int attempt,\n    HttpRequest request,\n    HttpResponse? response,\n    RhttpException? exception,\n  ) async {\n    ref.read(authProvider.notifier).state = await refresh();\n    return null;\n  }\n}\n```\n\nCheckout this [example](https://github.com/Tienisto/rhttp/blob/main/rhttp/example/lib/interceptor_riverpod.dart)\nto see how access tokens can be refreshed using Riverpod.\n\n## Error Handling\n\n### ➤ Exceptions\n\nAll exceptions are subclasses of `RhttpException`.\n\nThe following exceptions can be thrown:\n\n| Exception                          | Description                                           |\n|------------------------------------|-------------------------------------------------------|\n| `RhttpCancelException`             | Request was canceled.                                 |\n| `RhttpTimeoutException`            | Request timed out.                                    |\n| `RhttpRedirectException`           | Too many redirects.                                   |\n| `RhttpStatusCodeException`         | Response has 4xx or 5xx status code.                  |\n| `RhttpInvalidCertificateException` | Server certificate is invalid.                        |\n| `RhttpConnectionException`         | Connection error. (no internet, server not reachable) |\n| `RhttpClientDisposedException`     | Client is already disposed.                           |\n| `RhttpInterceptorException`        | Interceptor threw an exception.                       |\n| `RhttpUnknownException`            | Unknown error occurred.                               |\n\n### ➤ Throw on Status Code\n\nBy default, an exception is thrown if the response has a 4xx or 5xx status code.\nYou can disable this behavior by setting `throwOnStatusCode` to `false`.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    throwOnStatusCode: false,\n  ),\n);\n```\n\n## Compatibility Layer\n\nYou can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package,\nthereby exposing the same API as the default HTTP client in the Dart ecosystem.\n\nThis comes with some downsides, such as:\n\n- inferior type safety due to the flaw that `body` is of type `Object?` instead of an explicit type\n- body of type `Map` is implicitly interpreted as `x-www-form-urlencoded` that cannot be changed\n- no support for cancellation\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\nBecause this client is compatible with [http](https://pub.dev/packages/http),\nyou can use [dio_compatibility_layer](https://pub.dev/packages/dio_compatibility_layer)\nto use rhttp with the [dio](https://pub.dev/packages/dio) package.\n\n```dart\nFuture<Dio> createDioClient() async {\n  final dio = Dio();\n  final compatibleClient = await RhttpCompatibleClient.create(); // or createSync()\n  dio.httpClientAdapter = ConversionLayerAdapter(compatibleClient);\n  return dio;\n}\n```\n\nIf you are looking for a replacement for `HttpClient` of `dart:io`, you can use the `IoCompatibleClient`:\n\n```dart\nimport 'dart:io';\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  final client = await IoCompatibleClient.create();\n  final request = await client.getUrl(Uri.parse('https://example.com'));\n  final response = await request.close();\n\n  print(response.statusCode);\n  print(await response.transform(utf8.decoder).join());\n}\n```\n\n## License\n\nMIT License\n\nCopyright (c) 2024-2026 Tien Do Nam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","isInternal":false,"tokens":6736,"sizeBytes":27027}],"systemPromptSnippet":"<agent_rules repository=\"Notsfsssf/pixez-flutter\">\n\n<!-- Skill/Rule: benchmark Documentation (plugins/rhttp/benchmark/README.md) -->\n# benchmark\n\nStart server:\n\n```shell\ncd nodejs\nnode server.js\n```\n\nStart benchmark:\n\n```shell\ncd benchmark\nflutter run --release\n```\n\n## 1 KB x 10000\n- rhttp: 1010 ms\n- http: 2174 ms\n- dio: 2758 ms\n\n## 10 MB x 100\n- rhttp: 2394 ms\n- http: 12527 ms\n- dio: 13091 ms\n\n\n<!-- Skill/Rule: rhttp Documentation (plugins/rhttp/README.md) -->\nrhttp/README.md\n\n<!-- Skill/Rule: build_tool Documentation (plugins/rhttp/rhttp/cargokit/build_tool/README.md) -->\n/// This is copied from Cargokit (which is the official way to use it currently)\n/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin\n\nA sample command-line application with an entrypoint in `bin/`, library code\nin `lib/`, and example unit test in `test/`.\n\n\n<!-- Skill/Rule: LaunchImage.imageset Documentation (plugins/rhttp/rhttp/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md) -->\n# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.\n\n<!-- Skill/Rule: example Documentation (plugins/rhttp/rhttp/example/README.md) -->\n# rhttp_example\n\nDemonstrates how to use the rhttp plugin.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)\n\nFor help getting started with Flutter development, view the\n[online documentation](https://docs.flutter.dev/), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n\n\n<!-- Skill/Rule: rhttp Documentation (plugins/rhttp/rhttp/README.md) -->\n# rhttp\n\n[![pub package](https://img.shields.io/pub/v/rhttp.svg)](https://pub.dev/packages/rhttp)\n![ci](https://codeberg.org/Tienisto/rhttp/actions/workflows/ci.yml/badge.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nMake HTTP requests using Rust for Flutter developers.\n\n## About\n\nThis package is a Dart wrapper around the [reqwest](https://crates.io/crates/reqwest) crate, which is a fast and reliable HTTP client for Rust.\nFor optimal performance, we use FFI with [flutter_rust_bridge](https://pub.dev/packages/flutter_rust_bridge) to call Rust code.\n\nThe default HTTP client in Dart is part of `dart:io`, which lacks configurability and performance compared to other HTTP clients.\nFurthermore, HTTP/2 and HTTP/3 are either missing or not supported by default.\n\nCompared to [cronet_http](https://pub.dev/packages/cronet_http) and [cupertino_http](https://pub.dev/packages/cupertino_http), this package offers a unified, feature-rich API\nthat also works on Windows and Linux.\n\nThe APK size will increase by 2 MB on arm64 and 6 MB if compiled for all architectures (x64, arm32, arm64).\n\nWeb is currently not supported.\n\n## Features\n\n- ✅ HTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 support\n- ✅ TLS 1.2 and 1.3 support\n- ✅ Connection pooling\n- ✅ Interceptors\n- ✅ Retry (optional)\n- ✅ Certificate pinning\n- ✅ Proxy support\n- ✅ Custom DNS resolution\n- ✅ Cookies\n- ✅ Strong type safety\n- ✅ DevTools support ([Network tab](https://docs.flutter.dev/tools/devtools/network))\n- ✅ Compatible with [dart:io](https://api.dart.dev/stable/dart-io/HttpClient-class.html), [http](https://pub.dev/packages/http), and [dio](https://pub.dev/packages/dio)\n\n## Benchmark\n\nrhttp is much faster at downloading large files and a bit faster at downloading small files compared to the default HTTP client in Dart.\n\n| Small Files (1 KB)                                                                                 | Large Files (10 MB)                                                                                |\n|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|\n| ![benchmark-small](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-small.png) | ![benchmark-large](https://codeberg.org/Tienisto/rhttp/raw/branch/main/benchmark/result-large.png) |\n\nReferred packages: [dio](https://pub.dev/packages/dio) (5.5.0+1), [http](https://pub.dev/packages/http) (1.2.2), [rhttp](https://pub.dev/packages/rhttp) (0.3.0)\n\nCheckout the benchmark code [here](https://github.com/Tienisto/rhttp/tree/main/benchmark).\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Request Basics](#request-basics)\n  - [HTTP methods](#-http-methods)\n  - [Request query parameters](#-request-query-parameters)\n  - [Request Headers](#-request-headers)\n  - [Request Body](#-request-body)\n  - [Response Body](#-response-body)\n- [Request Lifecycle](#request-lifecycle)\n  - [Cancel Requests](#-cancel-requests)\n  - [Progress](#-progress)\n- [Client Settings](#client-settings)\n  - [Connection Reuse](#-connection-reuse)\n  - [Keep-Alive](#-keep-alive)\n  - [Timeout](#-timeout)\n  - [Base URL](#-base-url)\n  - [HTTP version](#-http-version)\n  - [TLS version](#-tls-version)\n  - [TLS Server Name Indication (SNI)](#-tls-server-name-indication-sni)\n  - [Certificate Pinning](#-certificate-pinning)\n  - [Root certificate source](#-root-certificate-source)\n  - [Client Authentication](#-client-authentication--mutual-tls)\n  - [Disable certificate verification](#-disable-certificate-verification)\n  - [Proxy](#-proxy)\n  - [Redirects](#-redirects)\n  - [DNS resolution](#-dns-resolution)\n  - [Cookies](#-cookies)\n  - [User-Agent](#-user-agent)\n- [Intercept](#intercept)\n  - [Interceptors](#-interceptors)\n  - [RetryInterceptor](#-retryinterceptor)\n- [Error Handling](#error-handling)\n  - [Exceptions](#-exceptions)\n  - [Throw on Status Code](#-throw-on-status-code)\n- [Compatibility Layer](#compatibility-layer)\n\n## Getting Started\n\n### ➤ Installation\n\n1. Install Rust via [rustup](https://rustup.rs/).\n   - Rust 1.80.0 or later is required.\n2. For Android: Install [Command-line tools](https://stackoverflow.com/questions/68236007/i-am-getting-error-cmdline-tools-component-is-missing-after-installing-flutter)\n   - Make sure to have the latest NDK installed. [#44](https://github.com/Tienisto/rhttp/issues/44)\n3. Add `rhttp` to `pubspec.yaml`:\n\n```yaml\ndependencies:\n  rhttp: <version>\n```\n\n### ➤ Initialization\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init(); // add this\n  runApp(MyApp());\n}\n```\n\n### ➤ Usage\n\n```dart\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  // Make a GET request\n  HttpTextResponse response = await Rhttp.get('https://example.com');\n  \n  // Read the response\n  int statusCode = response.statusCode;\n  String body = response.body;\n}\n```\n\nAlternatively, you can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package.\n\nFor more information, see [Compatibility Layer](#compatibility-layer).\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\n## Request Basics\n\n### ➤ HTTP methods\n\nYou can make requests using different HTTP methods:\n\n```dart\n// Pass the method as an argument\nawait Rhttp.requestText(method: HttpMethod.post, url: 'https://example.com');\n\n// Use the helper methods\nawait Rhttp.post('https://example.com');\n```\n\n### ➤ Request query parameters\n\nYou can add query parameters to the URL:\n\n```dart\nawait Rhttp.get('https://example.com', query: {'key': 'value'});\n```\n\n### ➤ Request Headers\n\nYou can add headers to the request:\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  headers: const HttpHeaders.map({\n    HttpHeaderName.contentType: 'application/json',\n  }),\n);\n```\n\n### ➤ Request Body\n\nYou can add a body to the request. There are different types of bodies you can use:\n\n**Text**\n\nPass a string to the `HttpBody.text` constructor.\n\n```dart\n// Raw body\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.text('raw body'),\n);\n```\n\n**JSON**\n\nPass a JSON data structure to the `HttpBody.json` constructor.\n\nThe Content-Type header will be set to `application/json` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.json({'key': 'value'}),\n);\n```\n\n**Binary**\n\nPass a `Uint8List` to the `HttpBody.bytes` constructor.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(Uint8List.fromList([0, 1, 2])),\n);\n```\n\n**Stream**\n\nPass a `Stream<List<int>>` to the `HttpBody.stream` constructor.\n\nIt is recommended to also provide a `length` to automatically set the `Content-Length` header.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.stream(\n    Stream.fromIterable([[1, 2, 3]]),\n    length: 3,\n  ),\n);\n```\n\n**Form**\n\nPass a flat map to the `HttpBody.form` constructor.\n\nThe Content-Type header will be set to `application/x-www-form-urlencoded` if not provided.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.form({'key': 'value'}),\n);\n```\n\n**Multipart**\n\nPass a map of `MultipartItem` to the `HttpBody.multipart` constructor.\n\nThe Content-Type header will be overridden to `multipart/form-data` with a random boundary.\n\n```dart\nawait Rhttp.post(\n  'https://example.com',\n  body: HttpBody.multipart({\n    'name': const MultipartItem.text(\n      text: 'Tom',\n    ),\n    'profile_image': MultipartItem.bytes(\n      bytes: Uint8List.fromList(bytes),\n      fileName: 'image.jpeg',\n    ),\n  }),\n)\n```\n\n### ➤ Response Body\n\nTo let Rust do most of the work, you must specify the expected response body type before making the request.\n\n```dart\nHttpTextResponse response = await Rhttp.getText('https://example.com');\nString body = response.body;\n\nHttpBytesResponse response = await Rhttp.getBytes('https://example.com');\nUint8List body = response.body;\n\nHttpStreamResponse response = await Rhttp.getStream('https://example.com');\nStream<Uint8List> body = response.body;\n```\n\nThey all extend the `HttpResponse` class, which contains the following properties:\n\n| Property                                  | Description                                                     |\n|-------------------------------------------|-----------------------------------------------------------------|\n| `String? remoteIp`                        | The remote IP address of the server that sent the response.     |\n| `HttpRequest request`                     | The HTTP request that this response is associated with.         |\n| `HttpVersion version`                     | The HTTP version of this response.                              |\n| `int statusCode`                          | The HTTP status code of this response.                          |\n| `List<(String, String)> headers`          | The HTTP headers of this response.                              |\n| `Map<String, String> headerMap`           | Response headers converted as a map.                            |\n| `Map<String, List<String>> headerMapList` | Response headers converted as a map respecting multiple values. |\n\n## Request Lifecycle\n\n### ➤ Cancel Requests\n\nYou can cancel a request by providing a `CancelToken`.\n\nIf the same `CancelToken` is used for multiple requests, all requests will be canceled.\n\nIf a canceled `CancelToken` is used for a request, the request will be canceled immediately.\n\n```dart\nfinal cancelToken = CancelToken();\nfinal request = Rhttp.get(\n   'https://example.com',\n   cancelToken: cancelToken,\n);\n\n// Cancel the request\ncancelToken.cancel();\n\n// Will throw a `RhttpCancelException`\nawait request;\n```\n\n### ➤ Progress\n\nYou can observe the progress of the request, by providing `onSendProgress` and `onReceiveProgress` callbacks.\n\nPlease note that request and response bodies must be either `Stream` or `Uint8List`.\n\nThe parameter `total` can be `-1` if the total size is unknown.\n\nIt always emits the final value with `sent` / `received` and `total` being equal after the request is finished.\n\n```dart\nfinal request = Rhttp.post(\n  'https://example.com',\n  body: HttpBody.bytes(bytes),\n  onSendProgress: (sent, total) {\n    print('Sent: $sent, Total: $total');\n  },\n  onReceiveProgress: (received, total) {\n    print('Received: $received, Total: $total');\n  },\n);\n```\n\n## Client Settings\n\n### ➤ Connection Reuse\n\nTo improve performance, it is recommended to create a client and reuse it for multiple requests.\n\nThis allows you to reuse connections (with same servers).\nFurthermore, it avoids the overhead of creating a new client for each request.\n\n```dart\nfinal client = await RhttpClient.create();\n\nawait client.get('https://example.com');\n```\n\nYou can dispose the client when you are done with it:\n\n```dart\nclient.dispose();\n```\n\nTo create a client synchronously, use `RhttpClient.createSync`.\nThis should only be called during app start to avoid blocking the UI thread.\n\n```dart\nfinal client = RhttpClient.createSync();\n```\n\n### ➤ Keep-Alive\n\nBy default, connections are not kept alive. On HTTP/2, the same connection\nis reused for multiple requests that are done on the same time, but the socket\nis closed immediately after the last request is finished.\n\nSetting `keepAliveTimeout` to a value greater than `0` will keep the socket \nopen when idle for the specified duration, both in HTTP/1.1 and HTTP/2.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      keepAliveTimeout: Duration(seconds: 60),\n      keepAlivePing: Duration(seconds: 30),\n    ),\n  ),\n);\n```\n\n### ➤ Timeout\n\nYou can specify the timeout for the request:\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    timeoutSettings: TimeoutSettings(\n      timeout: Duration(seconds: 10),\n      connectTimeout: Duration(seconds: 5),\n    ),\n  ),\n);\n```\n\n### ➤ Base URL\n\nAdd a base URL to the client to avoid repeating the same URL or to change the base URL easily.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    baseUrl: 'https://example.com',\n  ),\n);\n```\n\n### ➤ HTTP version\n\nYou can specify the HTTP version to use for the request.\nHTTP/1, HTTP/1.1, HTTP/2, and HTTP/3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    httpVersionPref: HttpVersionPref.http3,\n  ),\n);\n```\n\n### ➤ TLS version\n\nYou can specify the TLS version to use for the request.\nOnly TLS 1.2 and 1.3 are currently supported.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      minTlsVersion: TlsVersion.tls12,\n      maxTlsVersion: TlsVersion.tls13,\n    ),\n  ),\n);\n```\n\n### ➤ TLS Server Name Indication (SNI)\n\nControls the use of TLS server name indication.\n\nThis option is enabled by default.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      sni: false,\n    ),\n  ),\n);\n```\n\n### ➤ Certificate Pinning\n\nTo improve security, you can specify the expected server certificate.\n\nDue to limitations on Rust's side ([Github Issue](https://github.com/seanmonstar/reqwest/issues/298)),\nyou need to either provide the full certificate chain, or the root certificate.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      trustedRootCertificates: [\n        '''-----BEGIN CERTIFICATE-----\nsome certificate\n-----END CERTIFICATE-----''',\n],\n    ),\n  ),\n);\n```\n\n### ➤ Root certificate source\n\nBy default, the root certificates provided by Mozilla (webpki) are used.\nAs of now, this is the most reliable option which requires no additional setup.\n\nYou can configure which root certificates are trusted by setting `TlsSettings.rootCertSource`.\n\n| Mode       | Description                                             | Notes                                                                                                   |\n|------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------|\n| `platform` | Use root certificates provided by the operating system. | Flexible, but may be inconsistent across platforms.                                                     |\n| `webpki`   | Use root certificates provided by Mozilla. (default)    | Consistent across platforms, but requires manual app updates. Root certs are valid for around 15 years. |\n| `none`     | Don't trust any root certificates.                      | Special use cases                                                                                       |\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      rootCertSource: RootCertSource.none,\n    ),\n  ),\n);\n```\n\n#### Android Proguard Exclusions (only required for `RootCertSource.platform`)\n\nThe `platform` mode relies on [`rustls-platform-verifier`](https://github.com/rustls/rustls-platform-verifier#proguard),\nwhose Java classes are stripped by Android's [R8 shrinker](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization)\nin release builds. The `webpki` (default) and `none` modes do not use these classes and need no Proguard configuration.\n\nIf you use `RootCertSource.platform`, add a `android/app/proguard-rules.pro` file if it does not already exist\nand add or append the following line:\n```\n-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; }\n```\nIf the proguard file did not exist earlier it must also be added to `android/app/build.gradle.kts`:\n```diff\n android {\n    ...\n    buildTypes {\n        release {\n            ...\n   \n+            proguardFiles(\n+                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n+                \"proguard-rules.pro\"\n             )\n         }\n     }\n}\n```\n\n### ➤ Client Authentication / mutual TLS\n\nYou can specify the client certificate and key to enable mutual TLS (mTLS).\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      clientCertificate: ClientCertificate(\n         certificate: clientCert,\n         privateKey: clientKey,\n      ),\n    ),\n  ),\n);\n```\n\n### ➤ Disable certificate verification\n\nThis is very insecure and should only be used for testing purposes.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    tlsSettings: TlsSettings(\n      verifyCertificates: false,\n    ),\n  ),\n);\n```\n\n### ➤ Proxy\n\nBy default, the system proxy is enabled.\n\nDisable system proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.noProxy(),\n  ),\n);\n```\n\nUse a custom proxy:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.proxy('http://localhost:8080'),\n  ),\n);\n```\n\nOnly proxy unencrypted HTTP traffic:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.static(\n      url: 'http://localhost:8080',\n      condition: ProxyCondition.onlyHttp,\n    ),\n  ),\n);\n```\n\nChain multiple proxies:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    proxySettings: ProxySettings.list([\n      StaticProxy(\n        url: 'http://localhost:8080',\n        condition: ProxyCondition.onlyHttp,\n      ),\n      StaticProxy(\n        url: 'http://localhost:8081',\n        condition: ProxyCondition.onlyHttps,\n      ),\n    ]),\n  ),\n);\n```\n\n### ➤ Redirects\n\nBy default, up to 10 redirects (e.g. HTTP 302) are followed.\n\nExceeding the maximum number of redirects will throw a `RhttpRedirectException`.\n\nYou can change the maximum number of redirects and whether to follow redirects:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    redirectSettings: RedirectSettings.limited(5), // or RedirectSettings.none()\n  ),\n);\n```\n\n### ➤ DNS resolution\n\nBy default, the system DNS resolver is used.\n\nYou can override the mapping of hostnames to IP addresses:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1'],\n      },\n    ),\n  )\n);\n```\n\nFor a more complex DNS resolution, you can construct a `DnsSettings.dynamic` object:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: ClientSettings(\n    dnsSettings: DnsSettings.dynamic(\n      resolver: (String host) async {\n        if (counter % 2 == 0) {\n          return ['127.0.0.1'];\n        } else {\n          return ['1.2.3.4'];\n        }\n      }\n    ),\n  )\n);\n```\n\nBy default, the conventional port is used. You can override this behaviour by specifying the port:\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    dnsSettings: DnsSettings.static(\n      overrides: {\n        'example.com': ['127.0.0.1:8080'],\n      },\n    ),\n  )\n);\n```\n\n### ➤ Cookies\n\nIt is possible to optionally activate automatic Cookie handling. This will store Cookies sent by the\nserver in an ephemeral Cookie [`Jar`](https://docs.rs/reqwest/latest/reqwest/cookie/struct.Jar.html).\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    cookieSettings: CookieSettings(storeCookies: true),\n  ),\n);\n```\n\n### ➤ User-Agent\n\nA convenient way to set the `User-Agent` header.\n\n```dart\nfinal client = await RhttpClient.create(\n  settings: const ClientSettings(\n    userAgent: 'MyApp/1.0',\n  ),\n);\n```\n\n## Intercept\n\n### ➤ Interceptors\n\nYou can add interceptors to the client to modify requests / responses, handle errors, observe requests, etc.\n\nAny exception thrown by an interceptor that is not a subclass of `RhttpException`\nwill be caught and wrapped in a `RhttpInterceptorException`.\n\n```dart\nclass TestInterceptor extends Interceptor {\n  @override\n  Future<InterceptorResult<HttpRequest>> beforeRequest(\n    HttpRequest request,\n  ) async {\n    return Interceptor.next(request.addHeader(\n      name: HttpHeaderName.accept,\n      value: 'application/json',\n    ));\n  }\n\n  @override\n  Future<InterceptorResult<HttpResponse>> afterResponse(\n    HttpResponse response,\n  ) async {\n    return Interceptor.next();\n  }\n\n  @override\n  Future<InterceptorResult<RhttpException>> onError(\n    RhttpException exception,\n  ) async {\n    return Interceptor.next();\n  }\n}\n```\n\nThere are 4 termination methods:\n\n- `Interceptor.next()`: Continue with the next interceptor.\n- `Interceptor.stop()`: Stop the interceptor chain.\n- `Interceptor.resolve()`: Resolve the request with the given response.\n- `throw RhttpException`: Throw an exception. The stack trace will be preserved.\n\nInstead of implementing the `Interceptor` class, you can use the `SimpleInterceptor` class:\n\n```dart\nfinal client = await RhttpClient.create(\n  interceptors: [\n    SimpleInterceptor(\n      onError: (exception) async {\n        if (exception is RhttpStatusCodeException && exception.statusCode == 401) {\n          // Log out\n        }\n        return Interceptor.next();\n      },\n    ),\n  ],\n);\n```\n\n### ➤ RetryInterceptor\n\nThere is a built-in `RetryInterceptor` that retries the request if it fails.\n\nDuring the retry, all interceptors except `RetryInterceptor` are called again.\n\n```dart\nclass RefreshTokenInterceptor extends RetryInterceptor {\n  final Ref ref;\n\n  RefreshTokenInterceptor(this.ref);\n\n  @override\n  int get maxRetries => 1;\n\n  @override\n  bool shouldRetry(HttpResponse? response, RhttpException? exception) {\n    return exception is RhttpStatusCodeException &&\n        (exception.statusCode == 401 || exception.statusCode == 403);\n  }\n\n  @override\n  Future<HttpRequest?> beforeRetry(\n    int attempt,\n    HttpRequest request,\n    HttpResponse? response,\n    RhttpException? exception,\n  ) async {\n    ref.read(authProvider.notifier).state = await refresh();\n    return null;\n  }\n}\n```\n\nCheckout this [example](https://github.com/Tienisto/rhttp/blob/main/rhttp/example/lib/interceptor_riverpod.dart)\nto see how access tokens can be refreshed using Riverpod.\n\n## Error Handling\n\n### ➤ Exceptions\n\nAll exceptions are subclasses of `RhttpException`.\n\nThe following exceptions can be thrown:\n\n| Exception                          | Description                                           |\n|------------------------------------|-------------------------------------------------------|\n| `RhttpCancelException`             | Request was canceled.                                 |\n| `RhttpTimeoutException`            | Request timed out.                                    |\n| `RhttpRedirectException`           | Too many redirects.                                   |\n| `RhttpStatusCodeException`         | Response has 4xx or 5xx status code.                  |\n| `RhttpInvalidCertificateException` | Server certificate is invalid.                        |\n| `RhttpConnectionException`         | Connection error. (no internet, server not reachable) |\n| `RhttpClientDisposedException`     | Client is already disposed.                           |\n| `RhttpInterceptorException`        | Interceptor threw an exception.                       |\n| `RhttpUnknownException`            | Unknown error occurred.                               |\n\n### ➤ Throw on Status Code\n\nBy default, an exception is thrown if the response has a 4xx or 5xx status code.\nYou can disable this behavior by setting `throwOnStatusCode` to `false`.\n\n```dart\nawait Rhttp.get(\n  'https://example.com',\n  settings: const ClientSettings(\n    throwOnStatusCode: false,\n  ),\n);\n```\n\n## Compatibility Layer\n\nYou can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package,\nthereby exposing the same API as the default HTTP client in the Dart ecosystem.\n\nThis comes with some downsides, such as:\n\n- inferior type safety due to the flaw that `body` is of type `Object?` instead of an explicit type\n- body of type `Map` is implicitly interpreted as `x-www-form-urlencoded` that cannot be changed\n- no support for cancellation\n\n```dart\nimport 'package:rhttp/rhttp.dart';\nimport 'package:http/http.dart' as http;\n\nvoid main() async {\n  await Rhttp.init();\n  \n  http.Client client = await RhttpCompatibleClient.create();\n  http.Response response = await client.get(Uri.parse('https://example.com'));\n\n  print(response.statusCode);\n  print(response.body);\n}\n```\n\nBecause this client is compatible with [http](https://pub.dev/packages/http),\nyou can use [dio_compatibility_layer](https://pub.dev/packages/dio_compatibility_layer)\nto use rhttp with the [dio](https://pub.dev/packages/dio) package.\n\n```dart\nFuture<Dio> createDioClient() async {\n  final dio = Dio();\n  final compatibleClient = await RhttpCompatibleClient.create(); // or createSync()\n  dio.httpClientAdapter = ConversionLayerAdapter(compatibleClient);\n  return dio;\n}\n```\n\nIf you are looking for a replacement for `HttpClient` of `dart:io`, you can use the `IoCompatibleClient`:\n\n```dart\nimport 'dart:io';\nimport 'package:rhttp/rhttp.dart';\n\nvoid main() async {\n  await Rhttp.init();\n  \n  final client = await IoCompatibleClient.create();\n  final request = await client.getUrl(Uri.parse('https://example.com'));\n  final response = await request.close();\n\n  print(response.statusCode);\n  print(await response.transform(utf8.decoder).join());\n}\n```\n\n## License\n\nMIT License\n\nCopyright (c) 2024-2026 Tien Do Nam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\n</agent_rules>"}