- 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
5.6 KiB
5.6 KiB
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 projectcargo build --release- Build optimized release versioncargo run- Run the server with default configcargo test- Run all unit testscargo test <test_name>- Run a specific testcargo test <module>::tests- Run tests in a specific modulecargo clippy- Run linter checks for code qualitycargo clippy --fix- Automatically fix clippy suggestionscargo clippy --bin <name>- Check specific binarycargo fmt- Format code according to Rust standardscargo check- Quick compile check without building
Common Test Patterns
cargo test config::tests- Run config module testscargo test request::tests- Run request handling testscargo 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::functionfor 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
constfor configuration values that don't change - Error handling with
Result<T, E>and?operator - Use
tracingfor logging, notprintln!in production code
Naming Conventions
PascalCasefor types, structs, enumssnake_casefor functions, variables, modulesSCREAMING_SNAKE_CASEfor constants- Use descriptive names that indicate purpose
Error Handling
- Use
io::Result<()>for I/O operations - Convert errors to appropriate types with
map_errwhen 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_SIZEconstant) - Validate TLS certificates properly
- Never expose directory listings
Testing Guidelines
- Use
tempfile::TempDirfor 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!andassert!for validations
Lint Checking
cargo clippy- Run linter checks for code qualitycargo clippy --fix- Automatically fix clippy suggestionscargo clippy --bin <name>- Check specific binarycargo 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 testbefore 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
.awaiton async calls - Prefer
tokio::fsoverstd::fsin 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\ntermination - 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-securitycrate 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/binis in PATH (addsource "$HOME/.cargo/env"to~/.bashrc) - Verify setup:
cargo clippy --version