tiny_http_client 0.1.0

Tiny synchronous HTTP/HTTPS GET and POST client
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# tiny_http_client

A small synchronous HTTP/HTTPS GET/POST client written in Rust.

`tiny_http_client` is designed for small applications and utilities that need basic HTTP/HTTPS functionality without pulling in a full-featured HTTP client stack.

The client uses:

- native Windows / macOS TLS via [`native-tls`]https://crates.io/crates/native-tls;
- optional Linux native TLS via [`native-tls`]https://crates.io/crates/native-tls;
- [`rustls`]https://crates.io/crates/rustls with [`ring`]https://crates.io/crates/ring and [`webpki-roots`]https://crates.io/crates/webpki-roots on Linux;
- Rust's standard networking and I/O APIs.

## Features

The crate provides the following Cargo features:

- `http-get` — enables HTTP GET requests;
- `http-post` — enables HTTP POST requests;
- `linux-native-tls` — enables `native-tls` on Linux and uses the operating system certificate store;
- `linux-own-cert-list` — enables an embedded CA certificate list on Linux.

By default, only `http-get` is enabled.

On Windows and macOS, `native-tls` is always used. On Linux, `rustls` is used by default.

```toml
[features]
default = ["http-get"]
http-get = []
http-post = []
linux-native-tls = ["dep:native-tls"]
linux-own-cert-list = []
```

To enable both GET and POST:

```toml
[dependencies]
tiny_http_client = {
    git = "https://github.com/ygguser/tiny_http_client",
    features = ["http-get", "http-post"]
}
```

If only POST is required:

```toml
[dependencies]
tiny_http_client = {
    git = "https://github.com/ygguser/tiny_http_client",
    default-features = false,
    features = ["http-post"]
}
```

## Installation

Add the following dependency to `Cargo.toml`:

```toml
[dependencies]
tiny_http_client = { git = "https://github.com/ygguser/tiny_http_client" }
```
Or use a specific revision:

```toml
[dependencies]
tiny_http_client = { git = "https://github.com/ygguser/tiny_http_client", rev = "26f03556e38417c1366ab86e08daeaebd95c7604" }
```

## Linux native TLS

On Linux, enable the `linux-native-tls` feature to use `native-tls` and the operating system certificate store:

```toml
[dependencies]
tiny_http_client = {
    git = "https://github.com/ygguser/tiny_http_client",
    default-features = false,
    features = ["http-get", "linux-native-tls"]
}
```

The `linux-native-tls` and `linux-own-cert-list` features are mutually exclusive.

## CA certificate list

The crate supports an optional `linux-own-cert-list` feature that embeds a selected set of CA root certificates directly into the binary.

This can be useful for small applications that only connect to a known set of HTTPS services and do not need the complete system or `webpki-roots` certificate store.

Enable the feature in `Cargo.toml`:
```toml
[dependencies]
tiny_http_client = {
    git = "https://github.com/ygguser/tiny_http_client",
    features = ["linux-own-cert-list"]
}
```
When `linux-own-cert-list` is enabled:

* only certificates from the crate's `certs/` directory are embedded;
* the certificates are stored in DER format;
* the normal `webpki-roots` certificate list is not used;
* on Windows / macOS, the feature has no effect and the native OS certificate store is used;
* on Linux, the embedded certificates are used as the TLS trust anchors.

The feature is intentionally disabled by default.

### Certificate files

Certificates used by `linux-own-cert-list` are stored in: `certs/*.der`

Each file should contain one CA certificate in DER format.

The certificate filenames are not important. The build script automatically finds all `.der` files in the `certs/` directory and generates the Rust source code required to embed them into the binary.

### Generating the certificate list

The repository contains a `get-certs.sh` script for generating a minimal CA certificate list for a specific set of HTTPS hosts.

The hosts are configured near the beginning of the script:
```bash
HOSTS="
github.com
api.github.com
objects.githubusercontent.com
github-releases.githubusercontent.com
"
```
The script:

1. connects to each HTTPS host;
2. obtains the certificates sent by the server;
3. verifies the certificate chain using the system OpenSSL trust store;
4. determines the root CA of the verified chain;
5. extracts the root CA from the system trust store;
6. converts it to DER format;
7. saves it in the `certs/` directory.

For example:
```
certs/
├── ISRG_Root_X1_96bcec06264976f3.der
├── USERTrust_ECC_Certification_Authority_4ff460d54b9c86da.der
└── ...
```
The script does not blindly trust certificates received from the server. Server-provided certificates are used only to determine and verify the certificate chain. The root CA is extracted from the local system trust store.

Run:

```bash
./get-certs.sh
```

The script requires OpenSSL and uses the system CA store to verify the certificate chains.

After updating the certificates, build the application with:
```bash
cargo build --release --features linux-own-cert-list
```
### Why use an own certificate list?

Embedding only the root certificates required by an application can reduce the amount of CA data included in the binary and makes the trust store independent of the operating system.

It is especially useful for small standalone utilities that communicate only with a limited number of HTTPS services.

For example, an application that only communicates with GitHub may only need the root CAs required by GitHub and its release/download infrastructure rather than a complete collection of public root certificates.

The application should regenerate the certificate list when the HTTPS services it uses change their certificate chains or when the relevant root certificates change.

## Basic GET usage

```rust
use tiny_http_client::get;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = get("https://example.com/")?;

    println!("HTTP status: {}", response.status);

    println!("{}", response.as_str()?);

    Ok(())
}
```

## GET request headers

Use `get_with_headers()` when custom HTTP headers are required:

```rust
use tiny_http_client::get_with_headers;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = get_with_headers(
        "https://api.github.com/repos/ygguser/peers_updater/releases/latest",
        &[
            ("User-Agent", "peers_updater"),
            ("Accept", "application/json"),
        ],
    )?;

    println!("HTTP status: {}", response.status);

    println!("{}", response.as_str()?);

    Ok(())
}
```
Header names and values containing CR or LF characters are rejected to prevent HTTP header injection.

## POST request

POST requests are enabled by the `http-post` feature.

The basic `post()` function sends a request body:

```rust
use tiny_http_client::post;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = post(
        "https://example.com/api",
        b"hello=world",
    )?;

    println!("HTTP status: {}", response.status);

    println!("{}", response.as_str()?);

    Ok(())
}
```

The request body is provided as a byte slice, so binary data can also be sent.

### POST request with headers

Use `post_with_headers()` when custom HTTP headers are required:

```rust
use tiny_http_client::post_with_headers;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = post_with_headers(
        "https://example.com/api",
        &[
            ("Content-Type", "application/x-www-form-urlencoded"),
            ("User-Agent", "my-client"),
        ],
        b"hello=world",
    )?;

    println!("HTTP status: {}", response.status);

    println!("{}", response.as_str()?);

    Ok(())
}
```

### JSON POST request

JSON can be sent by providing the appropriate `Content-Type` header:

```rust
use tiny_http_client::post_with_headers;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let body = br#"{"name":"test","value":123}"#;


    let response = post_with_headers(
        "https://example.com/api",
        &[
            ("Content-Type", "application/json"),
            ("Accept", "application/json"),
        ],
        body,
    )?;

    println!("HTTP status: {}", response.status);

    println!("{}", response.as_str()?);

    Ok(())
}
```

The crate does not provide JSON serialization or deserialization. Applications can use any JSON library they prefer, or construct JSON manually when appropriate.

## Response

The `Response` structure contains:
```rust
pub struct Response {
    pub status: u16,
    pub headers: Vec<(String, String)>,
    pub body: Vec<u8>,
}
```

## Status code

```rust
println!("{}", response.status);
```

## Response body as bytes

```rust
let data: &[u8] = response.as_bytes();
```

## Response body as UTF-8

```rust
let text = response.as_str()?;
println!("{}", text);
```

## Response headers

Use `header()` to retrieve a header without worrying about capitalization:

```rust
if let Some(content_type) = response.header("Content-Type") {
    println!("Content-Type: {}", content_type);
}
```

## Linux TLS options

On Windows and macOS, HTTPS connections always use `native-tls` and the native operating system certificate store.

On Linux, TLS implementation can be selected using features:

- no Linux TLS feature — `rustls` with Mozilla root certificates from `webpki-roots`;
- `linux-own-cert-list``rustls` with CA certificates embedded from the crate's `certs/` directory;
- `linux-native-tls``native-tls` with the operating system certificate store.

The `linux-native-tls` and `linux-own-cert-list` features are mutually exclusive.

The TLS connection performs normal server certificate verification against the selected root store.

No client certificates are required.

## Redirects

Supported HTTP redirects:

* 301 Moved Permanently
* 302 Found
* 303 See Other
* 307 Temporary Redirect
* 308 Permanent Redirect

Up to 5 redirects are followed automatically.

```rust
const MAX_REDIRECTS: usize = 5;
```

## Timeouts

The connection timeout is:
```rust
const CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
```
The same timeout is also applied to socket reads and writes.

## Design goals

The main goal of this crate is to provide a small dependency footprint and a simple API for applications that only need basic synchronous HTTP/HTTPS GET and POST requests.

The crate intentionally uses platform-specific TLS implementations:

* native TLS on Windows;
* native TLS on macOS;
* rustls on Linux.

This keeps each platform's implementation simple and lets Windows and macOS use their native TLS implementations and certificate stores.

It intentionally does not attempt to implement a complete HTTP client.

The crate currently does **not** provide:

* asynchronous I/O
* HTTP/2
* HTTP/3
* proxy support
* cookies
* connection pooling
* multipart requests
* PUT/PATCH/DELETE helpers
* automatic content compression
* automatic decompression
* authentication helpers

These features can be implemented by the application when needed.

## Error handling

All public request functions return:
```rust
Result<Response, Box<dyn std::error::Error>>
```
Examples of errors include:

* unsupported URL scheme
* DNS resolution failure
* TCP connection failure
* connection timeout
* TLS errors
* invalid HTTP responses
* invalid HTTP headers
* invalid chunked encoding
* too many redirects
* non-2xx HTTP status codes

For example:
```rust
match tiny_http_client::get("https://example.com/") {
    Ok(response) => {
        println!("Status: {}", response.status);
    }


    Err(error) => {
        eprintln!("HTTP request failed: {}", error);
    }
}
```

## Non-2xx responses

Responses with a status code outside the 200..=299 range are returned as errors.

## License

MIT License.

Copyright (C) ygguser 2026.

See [LICENSE](LICENSE) for the full license text.