惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
T
Troy Hunt's Blog
A
Arctic Wolf
Cyberwarzone
Cyberwarzone
L
Lohrmann on Cybersecurity
Simon Willison's Weblog
Simon Willison's Weblog
The Hacker News
The Hacker News
I
Intezer
T
Tenable Blog
L
LINUX DO - 热门话题
S
Securelist
WordPress大学
WordPress大学
月光博客
月光博客
MyScale Blog
MyScale Blog
T
Tor Project blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Blog — PlanetScale
Blog — PlanetScale
C
CERT Recently Published Vulnerability Notes
C
Cisco Blogs
SecWiki News
SecWiki News
Security Latest
Security Latest
Help Net Security
Help Net Security
云风的 BLOG
云风的 BLOG
The Cloudflare Blog
博客园 - 司徒正美
S
Secure Thoughts
F
Full Disclosure
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Schneier on Security
T
Threatpost
Schneier on Security
Schneier on Security
小众软件
小众软件
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research
P
Privacy & Cybersecurity Law Blog
Project Zero
Project Zero
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
TaoSecurity Blog
TaoSecurity Blog
Attack and Defense Labs
Attack and Defense Labs
Google Online Security Blog
Google Online Security Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Visual Studio Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - Franky
博客园 - 三生石上(FineUI控件)

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Using dio HTTP Client in Dart
Mathieu K · 2026-05-15 · via DEV Community

The low-level HTTP Client and a higher level implementation with the http package have been tested previously. This time, we will try another high level implementation of an HTTP client in Dart called dio. This client seems way more flexible than the previous clients, and embed great features. Furthermore, based on pub.dev, it looks like it's a popular choice. If you are interested to see some examples, you can look into example_dart/lib directory from their official repository. Anyway the first step to use it is to add it in the dependencies.

dart pub add dio
dart pub get

Enter fullscreen mode Exit fullscreen mode

This time, instead of of using our own quick and dirty HTTP server using netcat or Python http.server module, let use the free HTTP Echo Server service. The idea is to simply creates some requests to this server and print the data returned.

As usual, the first step is to import the package in bin/httpcat.dart.

import 'package:dio/dio.dart';

Enter fullscreen mode Exit fullscreen mode

Then, let create/configure a dio client object using the Dio class

final target = 'https://echo.free.beeceptor.com';   

final dio = Dio(
  BaseOptions(
    baseUrl: target,
    connectTimeout: const Duration(seconds: 5),
    receiveTimeout: const Duration(seconds: 5),
    contentType: null,
    followRedirects: true,
    maxRedirects: 2,
    persistentConnection: false,
    preserveHeaderCase: false,
    // receiveDataWhenStatusError; ...
    // requestEncoder: ...
    // responseDecoder: ...
    // responseType: ...                            
    // sendTimeout: ...                             
    // validateStatus: ...
    headers: {
      HttpHeaders.userAgentHeader: 'dio',
      'x-custom': 'custom'
    },
  )
);

Enter fullscreen mode Exit fullscreen mode

The Dio class constructor accepts a BaseOptions object, containing the options to configure for the HTTP client. As you can already see in the previous snippet, plenty of attributes are available:

  • baseUrl: set the URL target as String. It must be a valid URL;

  • connectTimeout: the connection timeout out as int, when the client is opening a new connection to the remote server. Setting it to null or to Duration.zero will wait for the connection forever;

  • contentType: set the Content-Type HTTP header as a String;

  • followRedirects: a Boolean value to define if the client should follow the redirection returned by the server;

  • maxRedirects: the maximum number of redirection the client is allowed to follow defined as int;

  • persistentConnection: define if the client should use a persistent connection or not as boolean;

  • preserveHeaderCase: keep the header case unmodified during the request, defined as boolean;

  • queryParameters: set the query parameters of the request as Map<String, dynamic>;

  • receiveDataWhenStatusError: I will need to check the code for this one, but I assume when an error is returned, the data received is dropped, if we set this property to true, we should then be able to receive the data from the error;

  • receiveTimeout: set the timeout as a Duration for the data sent by the server;

  • requestEncoder: define a custom request encoder using RequestEncoder type as function callback. A way to use it, like defined in the tests:

dio.options.requestEncoder = (data, _) => Future.value(utf8.encode(data));
// or
dio.options.requestEncoder = (data, _) => utf8.encode(data);

Enter fullscreen mode Exit fullscreen mode

  • responseDecoder: define a custom response decoder using ResponseDecoder type as function callback. It can be used like the following snippet from the test suite.
 dio.options.responseDecoder = (_, __, ___) => null;
// or
dio.options.responseDecoder = (_, __, ___) => Future.value('example');
// or
dio.options.responseDecoder = (_, __, ___) => 'example';

Enter fullscreen mode Exit fullscreen mode

  • responseType: set the kind of response wanted by the client. For example, it can be set to ResponseType.stream, ResponseType.plain or ResponseType.bytes.

  • sendTimeout: set the client send timeout, when data is being uploaded to the server;

  • validateStatus: set a custom function callback defining if a request status is good or not, using ValidateStatus type. Here an example of usage from the test suite, the status parameter is an integer representing the HTTP status code.

validateStatus: (status) => true;
// or
validateStatus: (status) => false;

Enter fullscreen mode Exit fullscreen mode

  • headers: configure a Map<String, dynamic> as HTTP Headers. Standardized HTTP Headers keys can be found in HttpHeaders class, defined as constants, for example acceptEncodingHeader contains the string accept-encoding. The code in charge of this part can be seen in dio/lib/src/headers.dart, it contains some constants;

After having created a Dio object, one can use different way to execute a request, but usually, two conventions can be used, using the baseUrl attribute set in the object, or pass it an Uri object.

So, before starting, the main() function will be modified to add a switch statement, and then, easily test each method from the CLI.

Future<int> main(List<String> arguments) async {
  if (arguments.length != 1) {
    print("Usage: httpcat TARGET");
    return 1;
  }

  switch (arguments[0]) {
    case "get": return getRequest();
    case "head": return headRequest();
    case "post": return postRequest();
    case "put": return putRequest(); 
    case "delete": return deleteRequest();
    case "patch": return patchRequest();
  }

  Uri? url = parseUrl(arguments[0]);
  if (url == null) return 1;  

  var ret = getUrl(url);
  return ret;
} 

Enter fullscreen mode Exit fullscreen mode

Then, the code will be called like that:

dart run ./bin/httpcat.dart get

Enter fullscreen mode Exit fullscreen mode

Another quick modification is required to help seeing the answers from the server. Let create a function called printResponse() to print the data from the response.

void printResponse(Response response) {
  print({
    'status': response.statusCode,
    'status_msg': response.statusMessage,
    'uri': response.realUri,
    'headers': response.headers,
    'data': response.data,
  });
}

Enter fullscreen mode Exit fullscreen mode

It will simply print to STDOUT the attributes available in a Response object.

GET Method

Probably one of the most used HTTP request is the GET one.

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity in the response and not the source text of the process, unless that text happens to be the output of the process.

-- RFC2616, section 9.3

getRequest() will use get() method to fetch /:

Future<int> getRequest() async {                    
  var response = await dio.get('/');   
  printResponse(response);                                            
  return 0;                                         
}

Enter fullscreen mode Exit fullscreen mode

Let run this code to see what it will return from the CLI.

$ dart run bin/httpcat.dart get
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 12:39:47 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: GET, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:38247, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Accept-Encoding: gzip, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}}}

Enter fullscreen mode Exit fullscreen mode

The answer from the server is printed alongside with the status and other information. The return data is a JSON object, containing my - modified - IP address, the headers and so on.

HEAD Method

The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.

-- RFC2616, section 9.4

headerRequest() function will use head() method and print the response.

Future<int> headRequest() async {
  var response = await dio.head('/');
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Let execute this function now.

$ dart run bin/httpcat.dart head
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
access-control-allow-origin: *
date: Wed, 13 May 2026 12:48:48 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: null}

Enter fullscreen mode Exit fullscreen mode

No data is returned there, it's normal. HTTP Head method does not return any data, like defined in the specifications.

POST Method

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:

  • Annotation of existing resources;
  • Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
  • Providing a block of data, such as the result of submitting a form, to a data-handling process;
  • Extending a database through an append operation.

-- RFC2616, section 9.5

postRequest() function will use post() method from the client. A POST request can pass some data, to do that, the string test_data is passed to the data parameter. The contentType attribute must also be updated, because dio is intercepting the POST request and will set it to JSON. Let set this to text/plain.

Future<int> postRequest() async {
  dio.options.contentType = 'text/plain';
  var response = await dio.post(
    '/',
    data: 'test_data'
  );
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

We can now invoke the command:

$ dart run bin/httpcat.dart post
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:27:11 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: POST, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:40459, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Content-Length: 9, Accept-Encoding: gzip, Content-Type: text/html, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}, rawBody: test_data}}

Enter fullscreen mode Exit fullscreen mode

As you can see, the data we have sent is stored in the rawBody field. POST, PUT and PATCH methods are really important, and another publication on this topic will be required, especially for the serialization part.

PUT Method

The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.

-- RFC2616, section 9.6

The PUT method is similar to the POST method. Here a putRequest() function is created, the content type is updated and the string test_data is sent to the server via the post() method.

Future<int> putRequest() async {
  dio.options.contentType = 'text/plain';
  var response = await dio.put(
    '/',
    data: 'test_data'
  );
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Similar result.

$ dart run bin/httpcat.dart put
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:32:18 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: PUT, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:44886, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Content-Length: 9, Accept-Encoding: gzip, Content-Type: text/plain, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}, rawBody: test_data}}

Enter fullscreen mode Exit fullscreen mode

DELETE Method

The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible location.

-- RFC2616, section 9.7

The DELETE method is closer to the GET method. deleteRequest() function is created, and the delete() method is called.

Future<int> deleteRequest() async {
  var response = await dio.delete('/');
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Here the returned data:

$ dart run bin/httpcat.dart delete
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:32:49 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: DELETE, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:40520, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Accept-Encoding: gzip, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}}}

Enter fullscreen mode Exit fullscreen mode

PATCH Method

The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI. The set of changes is represented in a format called a "patch document" identified by a media type. If the Request-URI does not point to an existing resource, the server MAY create a new resource, depending on the patch document type (whether it can logically modify a null resource) and permissions, etc.

-- RFC5789, section 2

The PATCH method is a recent addition to HTTP, but act similarly like the PUT and POST methods. patchRequest() function is created, the content type is updated and patch() method is called.

Future<int> patchRequest() async {
  dio.options.contentType = 'text/plain';
  var response = await dio.patch(
    '/',
    data: 'test_data'
  );
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Again, here the result:

$ dart run bin/httpcat.dart patch
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:33:28 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: PATCH, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:34690, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Content-Length: 9, Accept-Encoding: gzip, Content-Type: text/plain, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}, rawBody: test_data}}

Enter fullscreen mode Exit fullscreen mode

Downloading Files

dio is also offering two helpers to download file and store save locally via download() and downloadURI() methods. Let try to download the latest official OpenBSD banner locally using the downloadUri() method this time.

Future<int> downloadRequest() async {
  final url = Uri.parse('https://www.openbsd.org/images/puffy78.gif');
  var response = await dio.downloadUri(
    url,
    (_) => './puffy78.gif',
  );
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

downloadRequest() function is creating a new Uri object from a valid URL, in this case, from the OpenBSD website. Then, the url and an anonymous function is passed to the downloadUri() method. Finally, the response is displayed on STDOUT and the function return 0.

The anonymous function is taking one argument, a Headers object and must return a String representing where the file will be stored.

Let execute that:

$ dart run bin/httpcat.dart download
{status: 200, status_msg: OK, uri: https://www.openbsd.org/images/puffy78.gif, headers: content-type: image/gif
connection: close
last-modified: Wed, 22 Oct 2025 07:18:46 GMT
date: Thu, 14 May 2026 03:50:12 GMT
server: OpenBSD httpd
content-length: 56669
redirects: 0
uri: https://www.openbsd.org/images/puffy78.gif
, data: Instance of 'ResponseBody'}

$ ls -lhart puffy78.gif 
-rw-rw-r-- 1 user user 56K 14 mai   05:50 puffy78.gif

Enter fullscreen mode Exit fullscreen mode

It seems the function correctly downloaded the file, and when opening it with feh, the gif looks good.

What about OPTIONS, TRACE and/or custom HTTP Methods?

At this time, OPTIONS and TRACE HTTP methods have not been implemented in dio, but it could be possible to craft them using the request() method and setting the method attribute from the Options class to the correct String.

OPTIONS method can be useful when using CORS and then, can be sometime used by developers or administrator to check if everything is right.

The OPTIONS HTTP method requests permitted communication options for a given URL or server. This can be used to test the allowed HTTP methods for a request, or to determine whether a request would succeed when making a CORS preflighted request.

-- Mozilla Developer website

This time, the request() method can be used. This one permits to modify the Options object containing the method attribute, defined as String.

Unfortunately, the echo service does not support OPTIONS nor TRACE HTTP method, we will need to use nc -kl 8080 for this test.

Future<int> customRequest(String method) async {
  dio.options.method = method;
  var response = await dio.request('http://localhost:8080/');
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

We are not really interested of the response here, but more about what the client will send to the server. From the netcat side, here the raw output:

$ nc -kl 8080
OPTIONS / HTTP/1.1
user-agent: dio
connection: close
x-custom: custom
accept-encoding: gzip
content-length: 0
host: localhost:8080

Enter fullscreen mode Exit fullscreen mode

The client is correctly using the method we have manually set. The same procedure can also be applied for the TRACE method.

What about HTTP/2?

HTTP/2 is more a recent protocol upgrading HTTP/1.1 by adding multiplexed connection and use binary format (instead of text) for headers.

HTTP/2 provides an optimized transport for HTTP semantics. HTTP/2 supports all of the core features of HTTP/1.1 but aims to be more efficient in several ways. The basic protocol unit in HTTP/2 is a frame (Section 4.1). Each frame type serves a different purpose. [...] Multiplexing of requests is achieved by having each HTTP request/response exchange associated with its own stream. [...] Flow control and prioritization ensure that it is possible to efficiently use multiplexed streams. [...] HTTP/2 adds a new interaction mode whereby a server can push responses to a client. [...] Because HTTP header fields used in a connection can contain large amounts of redundant data, frames that contain them are compressed.

-- RFC7549, section 2

What kind of advantage to use HTTP/2 instead of HTTP/1.1? Better performance, especially when it comes to send multiple requests in parallel. Unfortunately, it does not fix everything, like slow connections issues, or a way for the server to easily close a connection, those issues are still relevant with HTTP/2, mainly due to the network layer above it: TCP.

Most - if not all - modern HTTP servers are supporting it (nginx, apache2, cowboy...) , the same happens for modern clients. dio needs the http2_adapter plugin to support this protocol though.

dart pub add dio_http2_adapter
dart pub get

Enter fullscreen mode Exit fullscreen mode

import 'package:dio_http2_adapter/dio_http2_adapter.dart';

Enter fullscreen mode Exit fullscreen mode

Future<int> http2Request() async {
  dio.httpClientAdapter = Http2Adapter(
    ConnectionManager(idleTimeout: const Duration(seconds: 10)),
  );
  var response = await dio.get('/');
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

$ dart run bin/httpcat.dart http2
{status: 200, status_msg: null, uri: https://echo.free.beeceptor.com/, headers: access-control-allow-origin: *
alt-svc: h3=":443"; ma=2592000
content-type: application/json
date: Thu, 14 May 2026 07:52:12 GMT
vary: Accept-Encoding
via: 1.1 Caddy
, data: {method: GET, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:34174, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Via: 2.0 Caddy, X-Custom: custom, Accept-Encoding: gzip}, parsedQueryParams: {}}}

Enter fullscreen mode Exit fullscreen mode

I'm not really sure if it correctly using HTTP/2 protocol while using the echo service. Let add a logging feature to see if we can know a bit more there. To do that, we can add a new LogInterceptor object in the current interceptor queue.

Future<int> http2Request() async {
  dio.httpClientAdapter = Http2Adapter(
    ConnectionManager(idleTimeout: const Duration(seconds: 10)),
  );
  dio.interceptors.add(
    LogInterceptor(
      logPrint: (o) => print(o.toString()),
      request: true,
      requestBody: true,
      requestHeader: true,
      requestUrl: true,
      responseBody: true,
      responseHeader: true,
      responseUrl: true,
    )
  );
  var response = await dio.get('/');
  printResponse(response);
  return 0;
}

Enter fullscreen mode Exit fullscreen mode

Execute the code a second time with the logs now:

$ dart run bin/httpcat.dart http2
*** Request ***
uri: https://echo.free.beeceptor.com/
method: GET
responseType: ResponseType.json
followRedirects: true
persistentConnection: false
connectTimeout: 0:00:05.000000
sendTimeout: null
receiveTimeout: 0:00:05.000000
receiveDataWhenStatusError: true
extra: {}
headers:
 user-agent: dio
 x-custom: custom
data:
null

*** Response ***
uri: https://echo.free.beeceptor.com/
statusCode: 200
headers:
 access-control-allow-origin: *
 alt-svc: h3=":443"; ma=2592000
 content-type: application/json
 date: Thu, 14 May 2026 08:00:36 GMT
 vary: Accept-Encoding
 via: 1.1 Caddy
Response Text:
{"method":"GET","protocol":"https","host":"echo.free.beeceptor.com","path":"/","ip":"1.2.3.4:50442","headers":{"Host":"echo.free.beeceptor.com","User-Agent":"dio","Via":"2.0 Caddy","X-Custom":"custom","Accept-Encoding":"gzip"},"parsedQueryParams":{}}

{status: 200, status_msg: null, uri: https://echo.free.beeceptor.com/, headers: access-control-allow-origin: *
alt-svc: h3=":443"; ma=2592000
content-type: application/json
date: Thu, 14 May 2026 08:00:36 GMT
vary: Accept-Encoding
via: 1.1 Caddy
, data: {method: GET, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:50442, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Via: 2.0 Caddy, X-Custom: custom, Accept-Encoding: gzip}, parsedQueryParams: {}}}

Enter fullscreen mode Exit fullscreen mode

Yeah, still not really sure if we are using HTTP/2 there. Let use netcat instead, just to be certain.

$ nc -kl | hexdump -C
00000000  50 52 49 20 2a 20 48 54  54 50 2f 32 2e 30 0d 0a  |PRI * HTTP/2.0..|
00000010  0d 0a 53 4d 0d 0a 0d 0a  00 00 06 04 00 00 00 00  |..SM............|
00000020  00 00 02 00 00 00 00 00  00 5b 01 04 00 00 00 01  |.........[......|
00000030  00 07 3a 6d 65 74 68 6f  64 03 47 45 54 00 05 3a  |..:method.GET..:|
00000040  70 61 74 68 01 2f 00 07  3a 73 63 68 65 6d 65 04  |path./..:scheme.|
00000050  68 74 74 70 00 0a 3a 61  75 74 68 6f 72 69 74 79  |http..:authority|
00000060  09 6c 6f 63 61 6c 68 6f  73 74 00 0a 75 73 65 72  |.localhost..user|
00000070  2d 61 67 65 6e 74 03 64  69 6f 00 08 78 2d 63 75  |-agent.dio..x-cu|
00000080  73 74 6f 6d 06 63 75 73  74 6f 6d 00 00 00 00 01  |stom.custom.....|
00000090  00 00 00 01 00 00 04 03  00 00 00 00 01 00 00 00  |................|

Enter fullscreen mode Exit fullscreen mode

As you can see, HTTP/2.0 is printed on the netcat side and a part of the data received are in binary format, not text like in HTTP/1.1 version. So, it looks good!

Other examples can be found in dio_http2_adapter README.md and in the example/ directory.

What about HTTP/3?

HTTP/3 provides a transport for HTTP semantics using the QUIC transport protocol and an internal framing layer similar to HTTP/2. Once a client knows that an HTTP/3 server exists at a certain endpoint, it opens a QUIC connection. QUIC provides protocol negotiation, stream-based multiplexing, and flow control. Within each stream, the basic unit of HTTP/3 communication is a frame. Each frame type serves a different purpose. Multiplexing of requests is performed using the QUIC stream abstraction. Each request-response pair consumes a single QUIC stream. Streams are independent of each other, so one stream that is blocked or suffers packet loss does not prevent progress on other streams. [...] As in HTTP/2, request and response fields are compressed for transmission.

-- RFC9114, Section 2

Unfortunately, it seems QUIC and HTTP/3 are not currently supported everywhere, it will depend of the platform used. The package pure_dart_quic seems to be the solution here. I don't think the solution was currently very stable, and in fact, the addition of this protocol in nginx for example is from the 1.25.0 version (released May 2023). The cowboy server is also offering its implementation but not by default since the beginning of 2025 (version 2.13.0). So, it will be the main subject for another article, outside of dio (or perhaps to extend it).

Want More About dio?

If you are here, that means you enjoyed these notes... And you probably want to know even more about dio. Perhaps having a small glimpse of its usage in real world? You can easily find few open source projects using it:

  • Diohub: open-source unofficial GitHub mobile client

  • Flutter Crypto APP: Complete Flutter Application with Riverpod & Freezed + Dio for API REST.

  • Vexana: Vexana is easy to use network process with dio.

  • randomuser: Example project to present Dio, RxDart and Bloc

  • Flutter User Management App with Multi-Source Backend Support: Flutter-based user management application that demonstrates full CRUD (Create, Read, Update, Delete) operations. The app interacts with a RESTful API to manage users and their associated data, including posts, todos, and comments.

  • youtube_video: a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

And to end this section, a list of interesting dio plugins:

  • dio_file_uploader: A en_file_uploader plugin to handle the file upload using dio package.

  • datadome_flutter_dio: provides an interceptor that filters and validates all requests to ensure your app networking layer is protected with DataDome.

  • dio_builder: a builder pattern for configuring and creating instances of the Dio HTTP client.

  • embrace_dio: enable the embrace plugin to capture network requests made with the Dio package.

  • alice_dio: Alice is an HTTP Inspector tool for Flutter which helps debugging http requests.

  • dio_http_logger: A powerful network interceptor for Dio, providing comprehensive logging of requests, responses and errors.

  • dio_retry_plus: A plugin for dio that retries failed requests.

  • chunked_uploader: A plugin to upload files to server in chunks.

  • oauth_dio: A customizable oauth client with token storage and interceptors for dio.

  • dio_ansi_logger: A beautiful, Postman-style Dio interceptor that logs HTTP requests and responses with ANSI colors, structured formatting, and fully customizable themes.

  • dio_cache: A plugin for dio that caches responses in a database for better optimization and offline data access.

  • pl_api_helper: pl_api_helper is a Flutter plugin that simplifies API calls, caching, and data management with support for both Dio and HTTP clients.

References and Resources


Cover Image by Ilona Ivanova on Unsplash