[<img alt="github" src="https://img.shields.io/badge/github-rttfd/nanofish-37a8e0?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/rttfd/nanofish)
[<img alt="crates.io" src="https://img.shields.io/crates/v/nanofish.svg?style=for-the-badge&color=ff8b94&logo=rust" height="20">](https://crates.io/crates/nanofish)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-nanofish-bedc9c?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/nanofish)

# Nanofish
A lightweight, `no_std` HTTP client for embedded systems built on Embassy networking with zero-copy response handling.
Nanofish is designed for embedded systems with limited memory. It provides a simple HTTP client that works without heap allocation, making it suitable for microcontrollers and `IoT` devices. The library uses zero-copy response handling where response data is borrowed directly from user-provided buffers, keeping memory usage predictable and efficient.
## Key Features
- **Zero-Copy Response Handling** - Response data is borrowed directly from user-provided buffers with no copying
- **User-Controlled Memory** - You provide the buffer and control exactly how much memory is used
- **No Standard Library** - Full `no_std` compatibility with no heap allocations
- **Embassy Integration** - Built on Embassy's async networking
- **Complete HTTP Support** - All standard HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE, CONNECT)
- **Smart Response Parsing** - Automatic text/binary detection based on Content-Type headers
- **Easy Header Management** - Pre-defined constants and helper methods for common headers
- **Optional TLS Support** - HTTPS support with embedded-tls when enabled
- **Timeout & Retry Support** - Built-in handling for network issues
- **DNS Resolution** - Automatic hostname resolution
## Zero-Copy Architecture
Unlike traditional HTTP clients that copy response data multiple times, Nanofish uses a zero-copy approach:
**Traditional HTTP Clients:**
```shell
Network → Internal Buffer (copy #1) → Response Struct (copy #2) → User Code (copy #3)
```
**Nanofish Zero-Copy:**
```shell
Network → YOUR Buffer (direct) → Zero-Copy References → User Code (no copies!)
```
### Benefits:
- **Better Performance** - No memory copying overhead
- **Memory Efficient** - Uses only the memory you provide
- **Predictable** - No hidden allocations
- **Embedded-Friendly** - Works well in resource-limited environments
## Installation & Feature Flags
### Basic HTTP Support (Default)
```toml
[dependencies]
nanofish = "0.7.0"
```
### With TLS/HTTPS Support
```toml
[dependencies]
nanofish = { version = "0.7.0", features = ["tls"] }
```
### Available Features
- **`tls`** - Enables HTTPS/TLS support via `embedded-tls`
- When disabled (default): Only HTTP requests are supported
- When enabled: Full HTTPS support with TLS 1.2/1.3
## Quick Start
Here's a simple example showing how to use Nanofish:
```rust,ignore
use nanofish::{HttpClient, HttpHeader, ResponseBody, headers, mime_types};
use embassy_net::Stack;
async fn example(stack: &Stack<'_>) -> Result<(), nanofish::Error> {
...
// See crate docs for full async usage example
}
let client = HttpClient::new(unsafe { core::ptr::NonNull::dangling().as_ref() });
let mut response_buffer = [0u8; 8192];
let headers = [
HttpHeader::user_agent("Nanofish/0.6.0"),
HttpHeader::content_type(mime_types::JSON),
HttpHeader::authorization("Bearer token123"),
];
let custom_headers = [
HttpHeader { name: "X-Custom-Header", value: "custom-value" },
HttpHeader::new(headers::ACCEPT, mime_types::JSON),
];
let (response, bytes_read) = client.get(
"http://example.com/api/status",
&headers,
&mut response_buffer
).await?;
println!("Read {} bytes into buffer", bytes_read);
```
## Zero-Copy Benefits
```rust,ignore
// Traditional approach (copies data):
// 1. Read from network → internal buffer (copy #1)
// 2. Parse response → response struct (copy #2)
// 3. User gets → copied data (copy #3)
// Nanofish zero-copy approach:
// 1. Read from network → YOUR buffer (direct)
// 2. Parse response → references to YOUR buffer (zero-copy)
// 3. User gets → direct references to YOUR buffer (zero-copy)
let mut small_buffer = [0u8; 1024]; // For small responses
let mut large_buffer = [0u8; 32768]; // For large responses
// Same API, different memory usage - YOU decide!
let (small_response, _) = client.get(url, &headers, &mut small_buffer).await?;
let (large_response, _) = client.get(url, &headers, &mut large_buffer).await?;
```
## Header Convenience Features
Nanofish provides helpful APIs for working with HTTP headers:
### Pre-defined Header Constants
```rust
use nanofish::headers;
// Common header names
let content_type = headers::CONTENT_TYPE; // "Content-Type"
let authorization = headers::AUTHORIZATION; // "Authorization"
let user_agent = headers::USER_AGENT; // "User-Agent"
let accept = headers::ACCEPT; // "Accept"
```
### Pre-defined MIME Types
```rust
use nanofish::mime_types;
// Common MIME types
let json = mime_types::JSON; // "application/json"
let xml = mime_types::XML; // "application/xml"
let text = mime_types::TEXT; // "text/plain"
let html = mime_types::HTML; // "text/html"
```
### Convenience Methods
```rust
use nanofish::{HttpHeader, mime_types};
// Easy creation of common headers
let headers = [
HttpHeader::content_type(mime_types::JSON),
HttpHeader::authorization("Bearer your-token"),
HttpHeader::user_agent("MyApp/1.0"),
HttpHeader::accept(mime_types::JSON),
HttpHeader::api_key("your-api-key"),
];
```
## Response Handling
Nanofish automatically determines the appropriate response body type based on the Content-Type header:
```rust,ignore
use nanofish::ResponseBody;
// The response body is automatically parsed based on content type
match &response.body {
ResponseBody::Text(text) => {
println!("Text response: {}", text);
}
ResponseBody::Binary(bytes) => {
println!("Binary response: {} bytes", bytes.len());
}
ResponseBody::Empty => {
println!("Empty response");
}
}
if response.is_success() {
println!("Request successful! Status: {}", response.status_code);
}
if response.is_client_error() {
println!("Client error: {}", response.status_code);
}
if response.is_server_error() {
println!("Server error: {}", response.status_code);
}
// You can also check status directly on the status code:
if response.status_code.is_success() {
println!("Success!");
}
if let Some(content_length) = response.content_length() {
println!("Content length: {} bytes", content_length);
}
## HTTP Methods Support
Nanofish provides convenience methods for all standard HTTP verbs:
```rust,ignore
// All methods require a buffer and return (HttpResponse, bytes_read)
let mut buffer = [0u8; 4096];
// GET request
let (response, bytes_read) = client.get(
"http://api.example.com/users",
&headers,
&mut buffer
).await?;
// POST request with JSON body
let json_body = br#"{"name": "John", "email": "john@example.com"}"#;
let post_headers = [
HttpHeader::content_type(mime_types::JSON),
HttpHeader::authorization("Bearer token123"),
];
let (response, bytes_read) = client.post(
"http://api.example.com/users",
&post_headers,
json_body,
&mut buffer
).await?;
// PUT request
let (response, bytes_read) = client.put(
"http://api.example.com/users/123",
&headers,
update_data,
&mut buffer
).await?;
// DELETE request
let (response, bytes_read) = client.delete(
"http://api.example.com/users/123",
&headers,
&mut buffer
).await?;
// Other HTTP methods
let (response, _) = client.patch("http://api.example.com/users/123", &headers, patch_data, &mut buffer).await?;
let (response, _) = client.head("http://api.example.com/status", &headers, &mut buffer).await?;
let (response, _) = client.options("http://api.example.com", &headers, &mut buffer).await?;
let (response, _) = client.trace("http://api.example.com", &headers, &mut buffer).await?;
let (response, _) = client.connect("http://proxy.example.com", &headers, &mut buffer).await?;
```
All methods return a `Result<(HttpResponse, usize), Error>` where:
- `HttpResponse` contains zero-copy references to data in your buffer
- `usize` is the number of bytes read into your buffer
## Memory Efficiency Examples
Choose your buffer size based on your needs:
```rust,ignore
// Scenario 1: Memory-constrained device (1KB buffer)
let mut tiny_buffer = [0u8; 1024];
let (response, _) = client.get(url, &headers, &mut tiny_buffer).await?;
// Perfect for small API responses, status checks, etc.
// Scenario 2: Streaming large data (32KB buffer)
let mut large_buffer = [0u8; 32768];
let (response, bytes_read) = client.get(large_url, &headers, &mut large_buffer).await?;
// Handle larger responses, file downloads, etc.
// Scenario 3: Reuse the same buffer for multiple requests
let mut shared_buffer = [0u8; 8192];
for url in urls {
let (response, _) = client.get(url, &headers, &mut shared_buffer).await?;
process_response(&response);
// Buffer is reused for each request - no allocations!
}
```
## License
[MIT](license)