Back to blog
Jan 08, 2026
4 min read

How I Reduced HTTP Requests by 90% in WRTune

How I optimized network calls in WRTune by implementing JSON-RPC batching, reducing requests by 90% and improving performance.

When building WRTune, my goal was to bring the power of OpenWrt to your fingertips. But as I added more features—real-time dashboard stats, detailed interface monitoring, wireless signal tracking—I hit a bottleneck common in mobile development: App Chatter.

The Problem: The “Death by a Thousand Requests” 🔗

To show you a complete system dashboard, the app needs to know:

  1. System Info (Uptime, Board Name)
  2. Disk Usage (Mount points)
  3. Active Connections (conntrack)
  4. Connection Limits
  5. CPU Temperature (Conditional)
  6. Load Averages (Conditional)
  7. Memory Details (Cached)

In my initial implementation, this meant firing off 7 to 9 separate HTTP requests per polling cycle.

Why this is bad for your phone: 🔗

  • Radio Wakeups: Every request forces the mobile radio (WiFi or 5G) to wake up to a high-power state. Frequent requests keep the radio active, draining your battery.
  • Context Switching: The OS has to manage opening/closing sockets, SSL handshakes, and thread allocation for each call.
  • UI Jank: Heavy network activity on the main thread (or even managed via Futures) can cause stuttering if not careful.

Why this is bad for your Router: 🔗

Routers, especially embedded ones running OpenWrt, have limited CPUs.

  • Process Spawning: Generating 9 concurrent requests often means the router’s web server (uhttpd/nginx) has to spawn 9 CGI or Lua processes to handle them.
  • Overhead: 90% of the work is just handling the HTTP handshake and headers, not fetching the tiny bit of data we needed.

The Solution: JSON-RPC Batching 🔗

OpenWrt’s ubus (OpenWrt micro bus) exposes a JSON-RPC interface. A little-known but powerful feature of JSON-RPC 2.0 is Batch Requests.

Instead of saying:

“Hey router, give me CPU load.” “Hey router, give me RAM.” “Hey router, give me temp.”

I can say:

“Hey router, here is a list of 7 things I need. Please get them all and send them back in one package.”

The Implementation 🔗

I refactored the SystemRepository to bundle these inquiries.

Before (Pseudo-code):

final sysInfo = await api.call('system', 'info');
final disks = await api.call('luci', 'getMountPoints');
final conns = await api.call('file', 'read', {'path': '/proc...'});
// ... distinct waits or Future.wait([...])

After (Batching):

final batch = [
  BatchRequest(id: 1, method: 'system.info' ...),
  BatchRequest(id: 2, method: 'luci.getMountPoints' ...),
  BatchRequest(id: 3, method: 'file.read' ...),
  // ...
];

final results = await api.batchCall(batch);
// results[1] is System Info
// results[2] is Mount Points

I also applied this to the Interface Details screen, combining:

  1. Logical Interface Dump
  2. Physical Device Stats
  3. Wireless Radio Info

Into a single request.

The Impact 🔗

The results were immediate and drastic.

1. Network Overhead Decimated 🔗

I went from 9 requests per 5 seconds to 1 request.

  • Request Count Reduction: ~90%
  • Payload Efficiency: While the total data size is similar, I saved the overhead of 8 HTTP headers and 8 TCP handshakes.

2. Router Load Reduced 🔗

By sending a single batch, the router’s ubusd process handles the queue internally. The web server only handles one incoming connection. This means fewer interrupts and lower CPU usage on the router itself, keeping it cool and responsive for its main job: routing packets.

3. Snappier UI 🔗

With data arriving in a single synchronized payload, the UI updates “all at once” rather than popping in element by element. It feels solid and professional.

Conclusion 🔗

Optimization isn’t always about writing faster algorithms. Sometimes, it’s about being a polite guest on the network. By respecting the cost of communication and batching requests, WRTune now runs smoother on your phone and lighter on your router.

Technically implemented using Dart, Dio, and OpenWrt JSON-RPC.