# AGENTS.md - br-web-server
> AI agent guidelines for this Rust HTTP/WebSocket server library.
## Overview
Rust library crate for HTTP/1.0, HTTP/1.1, HTTP/2, WebSocket with TLS via rustls. Published on crates.io as `br-web-server` v0.5.15.
## Structure
```
br-web-server/
├── src/
│ ├── lib.rs # Entry + WebServer, Handler trait, enums (858 lines)
│ ├── config.rs # Config, TlsConfig (181 lines)
│ ├── request.rs # Request parsing (535 lines)
│ ├── response.rs # Response builder (1109 lines)
│ ├── websocket.rs # WebSocket impl (1183 lines)
│ ├── stream.rs # HTTP/HTTPS stream abstractions
│ ├── client.rs # HTTP client (feature-gated)
│ ├── client_response.rs # Client response (private)
│ └── url.rs # URL parsing
├── tests/ # 8 test files
├── examples/
│ ├── br_web_service.rs # Server example (primary)
│ ├── br_clinet.rs # Client example (typo)
│ └── br_service.rs # Duplicate server example
└── Cargo.toml
```
## Commands
```bash
# Build
cargo build # Debug
cargo build --release # Release
cargo build --features client # Client feature only
# Lint & Format (CI policy)
cargo clippy -- -D warnings # Strict lint - treats warnings as errors
cargo fmt --check # Check format
cargo fmt # Auto-format
# Test
cargo test # All tests
cargo test test_name # Single test by name
cargo test -- --nocapture # Show println! output
cargo test --test unit_tests # Run specific test file
# Run Examples
cargo run --example br_web_service # Server
cargo run --example br_clinet # Client
# Docs
cargo doc --open
```
## Public API
| `lib.rs` | `WebServer`, `Handler`, `HttpError`, `Method`, `Protocol`, `Uri`, `Status` | Core |
| `config` | `Config`, `TlsConfig` | Server config |
| `request` | `Request` | Incoming request |
| `response` | `Response`, `CookieOptions`, `SameSite` | Response builder |
| `websocket` | `Websocket`, `Message`, `CloseCode`, `USERS`, `SUBSCRIPTIONS` | WS support |
| `client` | `Client` | HTTP client (feature: `client`) |
## Code Conventions
### Imports (grouped, blank-line separated)
```rust
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
use json::{object, JsonValue};
use log::{error, info};
use crate::config::Config;
```
### Naming
| Structs/Enums | PascalCase | `WebServer`, `HttpError` |
| Functions/vars | snake_case | `handle_request` |
| Constants | SCREAMING_SNAKE | `FLAG_END_STREAM`, `MAX_FRAME_SIZE` |
### Enum Pattern (from/str conversion)
```rust
impl Method {
pub fn from(name: &str) -> Self {
match name.to_lowercase().as_str() {
"post" => Self::POST,
_ => Self::Other(name.to_string()),
}
}
pub fn str(&self) -> &str { ... }
}
```
### Error Handling
- Custom type: `HttpError { code: u16, body: String }`
- Use `Result<T, HttpError>` or `io::Result<T>`
- Propagate with `?` operator
### Thread Safety
- `Arc<Mutex<T>>` for shared state
- `std::sync::LazyLock` for statics (`USERS`, `SUBSCRIPTIONS`, `WS_NOTICE`)
- `DashMap` for concurrent maps
### Comments
- Chinese comments acceptable (project convention)
- Use `///` for public API docs
## Features
```toml
[features]
client = ["infer"] # HTTP client
server = [] # Server (no extra deps)
default = ["client", "server"]
```
## Dependencies (non-obvious)
| `json` | JSON (NOT serde_json) - use `object!{}` macro |
| `br-crypto` | base64, sha1, sha256, md5, urlencoding |
| `rustls` | TLS (not openssl) |
| `hpack` | HTTP/2 header compression |
| `dashmap` | Concurrent HashMap |
| `flate2` | Gzip compression |
| `brotli` | Brotli decompression |
## Anti-Patterns (THIS PROJECT)
1. **No serde_json** - Use `json` crate with `object!{}` macro
2. **Monolithic lib.rs** - Contains runtime logic, not just re-exports
3. **Thread-per-connection** - Each request spawns a thread (no async runtime)
4. **Private module leak** - `ClientResponse` in private module but returned by public API
## Safety Warnings
| `response.rs:250` | Path traversal protection - sanitizes request paths |
| `response.rs:410,539` | Panic conditions documented for file handling |
| `websocket.rs:978,980` | Close codes 1005/1006 reserved, must not send |
| `request.rs:311` | Fallback for non-standard content-type to avoid panic |
## Known Issues
- `examples/br_clinet.rs` - Typo (should be `br_client`)
- Duplicate examples: `br_service.rs` vs `br_web_service.rs`
- `examples/ssl/` contains committed key material
- HTTP/2 support is partial
- HTTP/3 not implemented
## Config Schema
```toml
[br_web_server]
debug = true
host = "0.0.0.0:3535"
https = false
log = true
public = "public"
webpage = "spa"
runtime = "runtime"
charset = "utf-8"
max_body_size = 10485760 # 10MB default
read_timeout = 30
write_timeout = 30
[br_web_server.tls]
certs = "ssl/server.pem"
key = "ssl/server.key"
```
## Handler Pattern
```rust
#[derive(Clone, Debug)]
struct MyHandler { out: Websocket }
impl Handler for MyHandler {
fn on_request(&mut self, request: Request, response: &mut Response) {
response.status(200).json(object! { "ok": true });
}
fn on_options(&mut self, response: &mut Response) { /* CORS */ }
fn on_open(&mut self) -> Result<(), HttpError> { Ok(()) } // WS
fn on_message(&mut self, msg: Message) -> Result<(), HttpError> { Ok(()) }
fn on_close(&mut self, code: CloseCode, reason: &str) {}
}
// Start server
WebServer::new_service(config, |out| Box::new(MyHandler { out }));
```
## WebSocket Globals
```rust
// Connected users map
pub static USERS: LazyLock<DashMap<String, Websocket>>
// Channel subscriptions
pub static SUBSCRIPTIONS: LazyLock<DashMap<String, HashSet<String>>>
// Notice queue
pub static WS_NOTICE: LazyLock<Mutex<Vec<NoticeMsg>>>
```
## Timeouts
| Server | 30s | 30s |
| Client | 10s | 10s |