- Add max_concurrent_requests config option (default: 1000) - Implement global AtomicUsize counter for concurrent request tracking - Return status 41 'Server unavailable' when limit exceeded - Proper counter management with decrements on all exit paths - Add comprehensive config validation (1-1,000,000 range) - Update documentation with rate limiting details - Add unit tests for config parsing - Thread-safe implementation using Ordering::Relaxed This provides effective DDoS protection by limiting concurrent connections to prevent server overload while maintaining configurability for different deployment scenarios.
128 lines
5.3 KiB
Markdown
128 lines
5.3 KiB
Markdown
# Overview
|
|
This project is a very simple gemini server which only serves static files,
|
|
nothing else. It is meant to be generic so other people can use it.
|
|
|
|
# Build/Test/Lint Commands
|
|
|
|
## Core Commands
|
|
- `cargo build` - Build the project
|
|
- `cargo build --release` - Build optimized release version
|
|
- `cargo run` - Run the server with default config
|
|
- `cargo test` - Run all unit tests
|
|
- `cargo test <test_name>` - Run a specific test
|
|
- `cargo test <module>::tests` - Run tests in a specific module
|
|
- `cargo clippy` - Run linter checks for code quality
|
|
- `cargo clippy --fix` - Automatically fix clippy suggestions
|
|
- `cargo clippy --bin <name>` - Check specific binary
|
|
- `cargo fmt` - Format code according to Rust standards
|
|
- `cargo check` - Quick compile check without building
|
|
|
|
## Common Test Patterns
|
|
- `cargo test config::tests` - Run config module tests
|
|
- `cargo test request::tests` - Run request handling tests
|
|
- `cargo test -- --nocapture` - Show println output in tests
|
|
|
|
# Code Style Guidelines
|
|
|
|
## Imports
|
|
- Group imports: std libs first, then external crates, then local modules
|
|
- Use `use crate::module::function` for internal imports
|
|
- Prefer specific imports over `use std::prelude::*`
|
|
- Keep imports at module level, not inside functions
|
|
|
|
## Code Structure
|
|
- Use `#[tokio::main]` for async main function
|
|
- Keep functions small and focused (single responsibility)
|
|
- Use `const` for configuration values that don't change
|
|
- Error handling with `Result<T, E>` and `?` operator
|
|
- Use `tracing` for logging, not `println!` in production code
|
|
|
|
## Naming Conventions
|
|
- `PascalCase` for types, structs, enums
|
|
- `snake_case` for functions, variables, modules
|
|
- `SCREAMING_SNAKE_CASE` for constants
|
|
- Use descriptive names that indicate purpose
|
|
|
|
## Error Handling
|
|
- Use `io::Result<()>` for I/O operations
|
|
- Convert errors to appropriate types with `map_err` when needed
|
|
- Use `unwrap()` only in tests and main() for unrecoverable errors
|
|
- Use `expect()` with meaningful messages for debugging
|
|
- Return early with `Err()` for validation failures
|
|
|
|
## Security Requirements
|
|
- **Critical**: Always validate file paths with `path_security::validate_path`
|
|
- Never construct paths from user input without validation
|
|
- Use timeouts for network operations (`tokio::time::timeout`)
|
|
- Limit request sizes (see `MAX_REQUEST_SIZE` constant)
|
|
- Validate TLS certificates properly
|
|
- Never expose directory listings
|
|
|
|
## Testing Guidelines
|
|
- Use `tempfile::TempDir` for temporary directories in tests
|
|
- Test both success and error paths
|
|
- Use `#[cfg(test)]` for test modules
|
|
- Create temporary test files in `tmp/` directory
|
|
- Test security boundaries (path traversal, invalid inputs)
|
|
- Use `assert_eq!` and `assert!` for validations
|
|
|
|
## Lint Checking
|
|
- `cargo clippy` - Run linter checks for code quality
|
|
- `cargo clippy --fix` - Automatically fix clippy suggestions
|
|
- `cargo clippy --bin <name>` - Check specific binary
|
|
- `cargo fmt` - Format code to match Rust standards
|
|
- **Run clippy before every commit** - Address all warnings before pushing code
|
|
- Current clippy warnings (2025-01-15):
|
|
- src/server.rs:16-17 - Unnecessary borrows on file_path
|
|
- src/logging.rs:31 - Match could be simplified to let statement
|
|
|
|
## Async Patterns
|
|
- Use `.await` on async calls
|
|
- Prefer `tokio::fs` over `std::fs` in async contexts
|
|
- Handle timeouts for network operations
|
|
- Use `Arc<Clone>` for shared data across tasks
|
|
|
|
## Gemini Protocol Specific
|
|
- Response format: "STATUS META\r\n"
|
|
- Status 20: Success (follow with MIME type)
|
|
- Status 41: Server unavailable (timeout, overload)
|
|
- Status 51: Not found (resource doesn't exist)
|
|
- Status 59: Bad request (malformed URL, protocol violation)
|
|
- Default MIME: "text/gemini" for .gmi files
|
|
- Default file: "index.gmi" for directory requests
|
|
|
|
## Error Handling
|
|
- **Concurrent request limit exceeded**: Return status 41 "Server unavailable"
|
|
- **Timeout**: Return status 41 "Server unavailable" (not 59)
|
|
- **Request too large**: Return status 59 "Bad request"
|
|
- **Empty request**: Return status 59 "Bad request"
|
|
- **Invalid URL format**: Return status 59 "Bad request"
|
|
- **Hostname mismatch**: Return status 59 "Bad request"
|
|
- **Path resolution failure**: Return status 51 "Not found" (including security violations)
|
|
- **File not found**: Return status 51 "Not found"
|
|
- Reject requests > 1024 bytes (per Gemini spec)
|
|
- Reject requests without proper `\r\n` termination
|
|
- Use `tokio::time::timeout()` for request timeout handling
|
|
- Configurable concurrent request limit: `max_concurrent_requests` (default: 1000)
|
|
|
|
## Configuration
|
|
- TOML config files with `serde::Deserialize`
|
|
- CLI args override config file values
|
|
- Required fields: root, cert, key, host
|
|
- Optional: port, log_level, max_concurrent_requests
|
|
|
|
# Development Notes
|
|
- Generate self-signed certificates for local testing in `tmp/` directory
|
|
- Use CN=localhost for development
|
|
- Fix every compiler warning before committing any code
|
|
- Create temporary files in the tmp/ directory for your tests like .gem files
|
|
or images, etc., so they are gitignored
|
|
- Use `path-security` crate for path validation
|
|
- Default port: 1965 (standard Gemini port)
|
|
- Default host: 0.0.0.0 for listening
|
|
- Log level defaults to "info"
|
|
|
|
## Environment Setup
|
|
- Install clippy: `rustup component add clippy`
|
|
- Ensure `~/.cargo/bin` is in PATH (add `source "$HOME/.cargo/env"` to `~/.bashrc`)
|
|
- Verify setup: `cargo clippy --version`
|