Back to flin
flin

El sistema de middleware

Cómo los archivos _middleware.flin de FLIN crean un pipeline jerárquico para el procesamiento de solicitudes: registro, autenticación, CORS, limitación de velocidad, todo a través de convenciones del sistema de archivos.

Thales & Claude | March 30, 2026 7 min flin
EN/ FR/ ES
flinrust

Todos los frameworks web tienen middleware. Express encadena funciones con app.use(). Django tiene configuraciones MIDDLEWARE. Rails tiene before_action. Koa encadena generadores. El concepto es universal: código que se ejecuta antes (o después) de tu manejador de ruta para realizar preocupaciones transversales como registro, autenticación, CORS y limitación de velocidad.

El sistema de middleware de FLIN es diferente en un aspecto fundamental: el middleware se define por la ubicación del archivo, no por el registro en código. Creas un archivo llamado _middleware.flin en un directorio, y se aplica automáticamente a cada ruta en ese directorio y todos los subdirectorios. Sin llamadas de registro. Sin arreglos de configuración. Sin errores de ordenamiento.

La convención _middleware.flin

Un archivo de middleware siempre se llama _middleware.flin. El prefijo de guion bajo señala al enrutador que este archivo no es una ruta, sino un paso de procesamiento que se ejecuta antes de las rutas en su directorio.

flin// app/_middleware.flin -- se aplica a TODAS las rutas

middleware {
    log_info("{request.method} {request.path} from {request.ip}")
    response.headers["X-Request-ID"] = generate_uuid()
    next()
}

El bloque middleware { ... } define la lógica de procesamiento. La llamada next() pasa el control al siguiente middleware o manejador de ruta. Si no se llama a next(), la cadena se detiene y la respuesta (si existe) se envía de vuelta al cliente.

Ejecución jerárquica del middleware

Los archivos de middleware forman una jerarquía basada en la profundidad del directorio. Cuando llega una solicitud, FLIN ejecuta el middleware desde la raíz hasta el directorio más específico:

Solicitud: GET /admin/users/42

Orden de ejecución:
1. app/_middleware.flin           (global: registro, ID de solicitud)
2. app/admin/_middleware.flin     (admin: verificación de autenticación)
3. app/admin/users/[id].flin     (manejador: buscar y devolver usuario)

Esto no es configurable. El orden siempre es raíz primero, profundidad creciente. Esto elimina la fuente más común de errores de middleware: el ordenamiento. En Express, poner app.use(cors()) después de app.use(authMiddleware) produce un comportamiento diferente que ponerlo antes. En FLIN, el orden está determinado por la estructura de directorios, que es visible y determinista.

flin// app/_middleware.flin
middleware {
    // Se ejecuta primero para cada solicitud
    log_info("Request: {request.method} {request.path}")
    response.headers["X-Request-ID"] = generate_uuid()
    next()
}
flin// app/api/_middleware.flin
middleware {
    // Se ejecuta segundo para solicitudes /api/*
    // Cabeceras CORS
    if request.method == "OPTIONS" {
        return response {
            status: 204
            headers: {
                "Access-Control-Allow-Origin": env("ALLOWED_ORIGIN", "*"),
                "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH",
                "Access-Control-Allow-Headers": "Content-Type, Authorization",
                "Access-Control-Max-Age": "86400"
            }
        }
    }

    response.headers["Access-Control-Allow-Origin"] = env("ALLOWED_ORIGIN", "*")
    rate_limit(request.ip, limit: 100, window: 60)
    next()
}
flin// app/admin/_middleware.flin
middleware {
    // Se ejecuta para solicitudes /admin/*
    if session.user == none {
        redirect("/login")
    }

    user = User.where(email == session.user).first
    if user == none || user.role != "Admin" {
        redirect("/")
    }

    request.user = user
    next()
}

Acciones del middleware

Un middleware puede hacer cuatro cosas:

1. Pasar de largo

Llama a next() para continuar la cadena. Opcionalmente modifica la solicitud o respuesta antes y después:

flinmiddleware {
    start = now()
    next()
    duration = now() - start
    log_info("Request took {duration}ms")
}

2. Cortocircuitar con una respuesta

Devuelve una respuesta para detener la cadena inmediatamente:

flinmiddleware {
    if request.headers["X-API-Key"] != env("API_KEY") {
        return response {
            status: 401
            body: { error: "Invalid API key" }
        }
    }
    next()
}

3. Redirigir

Usa redirect() para enviar al cliente a otro lugar:

flinmiddleware {
    if session.user == none {
        redirect("/login")
    }
    next()
}

4. Enriquecer la solicitud

Adjunta valores calculados a request para los manejadores posteriores:

flinmiddleware {
    token = headers["Authorization"]
    if token != "" && token.starts_with("Bearer ") {
        jwt = token.slice(7)
        claims = verify_token(jwt)
        if claims != none {
            request.user = claims
            request.user_id = claims.sub
        }
    }
    next()
}

Patrones matcher y exclude

A veces un middleware debe aplicarse a la mayoría de las rutas en su directorio pero omitir algunas. Las propiedades matcher y exclude manejan esto:

flin// app/_middleware.flin

middleware {
    matcher: ["/tasks/**", "/people/**", "/settings/**", "/files/**"]
    exclude: ["/", "/login", "/register", "/auth/**", "/api/**"]

    if session.user == none {
        redirect("/login")
    }
    next()
}

La propiedad matcher especifica a qué rutas se aplica este middleware. La propiedad exclude especifica rutas a omitir. Si no se especifica ninguna, el middleware se aplica a todo en su directorio y debajo.

Sintaxis de patrones: - /tasks -- coincidencia exacta - /tasks/** -- coincide con /tasks y todo lo que está debajo - /api/* -- coincide con un nivel debajo de /api pero no más profundo

La implementación: cadena de middleware

Internamente, FLIN construye una cadena de middleware para cada ruta en tiempo de inicio:

rustpub struct MiddlewareChain {
    handlers: Vec<CompiledMiddleware>,
}

impl MiddlewareChain {
    pub async fn execute(
        &self,
        ctx: &mut RequestContext,
        route_handler: &CompiledFunction,
        vm: &mut VirtualMachine,
    ) -> Result<Response, RuntimeError> {
        self.execute_at(0, ctx, route_handler, vm).await
    }

    async fn execute_at(
        &self,
        index: usize,
        ctx: &mut RequestContext,
        route_handler: &CompiledFunction,
        vm: &mut VirtualMachine,
    ) -> Result<Response, RuntimeError> {
        if index >= self.handlers.len() {
            // Todo el middleware pasó -- ejecutar manejador de ruta
            return vm.execute_route(route_handler, ctx);
        }

        let middleware = &self.handlers[index];

        // Verificar patrones matcher/exclude
        if !middleware.matches(&ctx.request.path) {
            return self.execute_at(index + 1, ctx, route_handler, vm).await;
        }

        // Ejecutar middleware con callback next()
        let next = || self.execute_at(index + 1, ctx, route_handler, vm);
        vm.execute_middleware(middleware, ctx, next).await
    }
}

La cadena es recursiva: cada middleware recibe una función next() que, cuando se llama, ejecuta el siguiente middleware en la cadena. El manejador de ruta se sitúa al final de la cadena y se ejecuta solo si todos los middleware llaman a next().

Patrones comunes de middleware

Middleware de autenticación

flin// app/admin/_middleware.flin

middleware {
    exclude: ["/admin/login"]

    if session.user == none {
        redirect("/admin/login")
    }

    admin = User.where(email == session.user && role == "Admin").first
    if admin == none {
        session.user = none
        redirect("/admin/login")
    }

    request.admin = admin
    next()
}

Middleware de limitación de velocidad

flin// app/api/_middleware.flin

middleware {
    rate_limit(request.ip, limit: 100, window: 60)

    response.headers["X-RateLimit-Limit"] = "100"
    response.headers["X-RateLimit-Remaining"] = to_text(rate_remaining(request.ip))

    next()
}

Middleware de registro

flin// app/_middleware.flin

middleware {
    request_id = generate_uuid()
    response.headers["X-Request-ID"] = request_id

    start = now()
    next()
    duration = now() - start

    log_info("[{request_id}] {request.method} {request.path} -> {response.status} ({duration}ms)")
}

Middleware CORS

flin// app/api/_middleware.flin

middleware {
    origin = env("ALLOWED_ORIGIN", "*")

    if request.method == "OPTIONS" {
        return response {
            status: 204
            headers: {
                "Access-Control-Allow-Origin": origin,
                "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
                "Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key",
                "Access-Control-Max-Age": "86400"
            }
        }
    }

    response.headers["Access-Control-Allow-Origin"] = origin
    next()
}

Por qué middleware basado en archivos

El enfoque de middleware basado en archivos tiene tres ventajas significativas sobre los sistemas basados en registro:

Visibilidad. Puedes ver el middleware que se aplica a una ruta mirando el árbol de directorios. En Express, debes rastrear a través de llamadas app.use() en múltiples archivos para entender qué se ejecuta antes de un manejador. En FLIN, los archivos _middleware.flin en la ruta desde la raíz hasta el manejador SON la cadena de middleware.

Localidad. El middleware de autenticación vive en el directorio admin/ porque se aplica a las rutas de administración. El middleware de API vive en el directorio api/ porque se aplica a las rutas de API. El middleware está cerca del código que protege, no en un archivo de configuración distante.

Imposibilidad de olvidar. No puedes exponer accidentalmente una ruta de administración olvidando registrar middleware. Si la ruta está en el directorio admin/ y admin/_middleware.flin verifica la autenticación, cada ruta en ese directorio está protegida. Agregar una nueva página de administración significa crear un archivo en el directorio admin/ -- el middleware se aplica automáticamente.

La contrapartida, como con el enrutamiento basado en archivos, es que el middleware no puede componerse dinámicamente en tiempo de ejecución. Para los casos de uso que FLIN tiene como objetivo -- aplicaciones web con flujos de solicitud predecibles -- esto es una característica, no una limitación.

En el próximo artículo, exploramos los guards -- el sistema de seguridad declarativa de FLIN que complementa el middleware con control de acceso por ruta.


Esta es la Parte 101 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abiyán y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - [99] Análisis automático de JSON y cuerpo de formulario - [100] Inyección de contexto de solicitud - [101] El sistema de middleware (estás aquí) - [102] Guards: seguridad declarativa para rutas

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles