Skip to content

Add configurable redirect status codes with zero-cost optimization#33

Merged
MinecraftFuns merged 5 commits intomainfrom
copilot/add-redirect-code-configuration
Dec 8, 2025
Merged

Add configurable redirect status codes with zero-cost optimization#33
MinecraftFuns merged 5 commits intomainfrom
copilot/add-redirect-code-configuration

Conversation

Copy link
Contributor

Copilot AI commented Dec 8, 2025

Implements configurable HTTP redirect status codes (301/302/303/307/308) via REDIRECT_STATUS_CODE environment variable, defaulting to 308. Replaces Redirect wrapper with direct (StatusCode, HeaderMap) construction for zero runtime overhead.

Changes

  • Config validation: RedirectMode enum validates redirect codes at startup via TryFrom<u16>, preventing invalid codes (e.g., 200, 404)

    • Permanent (308), Temporary (307), SeeOther (303), MovedPermanently (301), Found (302)
  • Zero-cost optimization: Store StatusCode directly in RedirectState, construct responses with (StatusCode, [(LOCATION, HeaderValue)]) for fast path or (StatusCode, HeaderMap) for timing headers

    • Eliminates wrapper allocation, no branching, identical header construction cost
  • Error handling: Graceful handling of invalid URLs with detailed logging but generic user messages to prevent information disclosure

Example

// Before: wrapper overhead
Redirect::permanent(&url.original_url).into_response()

// After: direct construction
(state.redirect_status, [(LOCATION, location_val)]).into_response()

Configuration:

REDIRECT_STATUS_CODE=307 ./lynx  # Temporary redirect
REDIRECT_STATUS_CODE=301 ./lynx  # Legacy permanent

Invalid codes rejected at startup with clear error message.

Original prompt

Can you add configuration options for the specific redirect code used (301/302/303/307/308) defaults to 308 if nothing is configured; use a custom type to validate the configuration options, and convert to Status Code in the State storage so you can use it directly during the redirect path without additional cost.

you should follow Rust best practices and write efficient code to maximize performance. attached is the documentation FYI.

To support configurable redirects (HTTP 301/302/307/308) without performance penalties, you should avoid the Redirect struct wrapper and instead construct the response directly using the (StatusCode, HeaderMap) or (StatusCode, [(HeaderName, HeaderValue)]) pattern. This is the "rawest" and fastest way to return a response in Axum, bypassing the slight overhead of the Redirect struct's internal assertions and construction.

Store the desired StatusCode in your RedirectState (or a config struct within it). Accessing this field is a simple memory read and introduces zero runtime overhead compared to the hardcoded constant, while the header construction cost remains identical to Redirect::permanent.

Here is the implementation:

use axum::{
    extract::{ConnectInfo, Path, State},
    http::{
        header::{self, HeaderMap, HeaderValue, LOCATION}, // Added HeaderValue, LOCATION
        StatusCode,
    },
    response::IntoResponse, // Removed Redirect
    Extension, Json,
};
use serde::Serialize;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Instant;

use super::middleware::RequestStart;
use crate::analytics::{AnalyticsAggregator, GeoIpService};
use crate::config::AnalyticsConfig;
use crate::storage::Storage;

pub struct RedirectState {
    pub storage: Arc<dyn Storage>,
    pub analytics_config: Option<AnalyticsConfig>,
    pub geoip_service: Option<Arc<GeoIpService>>,
    pub analytics_aggregator: Option<Arc<AnalyticsAggregator>>,
    pub enable_timing_headers: bool,
    // Add the configurable status code here. 
    // This is Copy (u16 wrapper), so access is extremely fast.
    pub redirect_status: StatusCode, 
}

/// Redirect to original URL
pub async fn redirect_url(
    State(state): State<Arc<RedirectState>>,
    Path(code): Path<String>,
    Extension(RequestStart(request_start)): Extension<RequestStart>,
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
    headers: HeaderMap,
) -> impl IntoResponse {
    let handler_start = Instant::now();

    // Get URL with metadata
    let lookup_result = state.storage.get(&code).await;

    match lookup_result {
        Ok(result) => {
            let cache_hit = result.metadata.cache_hit;
            let cache_time_ms = result
                .metadata
                .cache_duration
                .map(|d| d.as_millis() as u64)
                .unwrap_or(0);
            let db_time_ms = result
                .metadata
                .db_duration
                .map(|d| d.as_millis() as u64)
                .unwrap_or(0);

            match result.url {
                Some(url) => {
                    // Check if URL is active
                    if !url.is_active {
                        return (StatusCode::GONE, "This link has been deactivated")
                            .into_response();
                    }

                    if let Err(err) = state.storage.increment_click(&code).await {
                        tracing::warn!(short_code = %code, error = %err, "failed to buffer click increment");
                    }

                    // Record analytics if enabled
                    if let (Some(config), Some(geoip), Some(aggregator)) = (
                        &state.analytics_config,
                        &state.geoip_service,
                        &state.analytics_aggregator,
                    ) {
                        if config.enabled {
                            record_analytics(&code, &headers, addr.ip(), config, geoip, aggregator);
                        }
                    }

                    // Pre-calculate the Location header value.
                    // This mimics the work Redirect::permanent does (HeaderValue::try_from).
                    // We handle the error gracefully instead of panicking.
                    let location_val = match HeaderValue::try_from(&url.original_url) {
                        Ok(val) => val,
                        Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Invalid URL").into_response(),
                    };

                    let handler_time = handler_start.elapsed();
                    let total_time = request_start.elapsed();

                    // Create headers with tracing info
                    if state.enable_timing_headers {
                        let mut response_headers = HeaderMap::new();
                        response_headers.insert(LOCATION, location_val);
                        response_headers.insert(
                            "x-lynx-cache-hit",
                     ...

</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

Copilot AI and others added 4 commits December 8, 2025 16:09
Co-authored-by: MinecraftFuns <25814618+MinecraftFuns@users.noreply.github.com>
Co-authored-by: MinecraftFuns <25814618+MinecraftFuns@users.noreply.github.com>
Co-authored-by: MinecraftFuns <25814618+MinecraftFuns@users.noreply.github.com>
Co-authored-by: MinecraftFuns <25814618+MinecraftFuns@users.noreply.github.com>
Copilot AI changed the title [WIP] Add configuration options for redirect status codes Add configurable redirect status codes with zero-cost optimization Dec 8, 2025
Copilot AI requested a review from MinecraftFuns December 8, 2025 16:24
@MinecraftFuns MinecraftFuns marked this pull request as ready for review December 8, 2025 18:00
@MinecraftFuns MinecraftFuns merged commit f4c177f into main Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants