- Add concurrent connection handling with tokio::spawn for proper rate limiting - Send '41 Server unavailable' responses instead of dropping connections - Move request logger initialization earlier to enable rate limiting logs - Add logging for rate limited requests: 'Concurrent request limit exceeded' - Fix clippy warnings: needless borrows and match simplification - Update test script analysis to expect 41 responses for rate limiting
135 lines
5.6 KiB
Markdown
135 lines
5.6 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
|
|
|
|
## Testing
|
|
- Run `cargo test` before every commit to prevent regressions
|
|
- Pre-commit hook automatically runs full test suite
|
|
- Rate limiting integration test uses separate port for isolation
|
|
- All tests must pass before commits are allowed
|
|
- Test suite includes: unit tests, config validation, rate limiting under load
|
|
|
|
## 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`
|