A lo largo de su relativamente corta historia, el lenguaje de programación Rust ha cosechado elogios excepcionales junto con un ecosistema rico y maduro. Tanto Rust como Cargo (su sistema de compilación, interfaz de cadena de herramientas y administrador de paquetes) son tecnologías admiradas y deseadas en el panorama, y Rust mantiene una posición estable entre los 20 lenguajes principales del ranking de lenguajes de programación de RedMonk . Además, los proyectos que adoptan Rust a menudo muestran mejoras en la estabilidad y en los errores de programación relacionados con la seguridad (por ejemplo, los desarrolladores de Android cuentan una historia convincente de mejora puntual).
F5 ha estado observando estos desarrollos en torno a Rust y su comunidad de Rustaceans con entusiasmo durante algún tiempo. Hemos tomado nota de ello y promovemos activamente el lenguaje , su cadena de herramientas y su adopción en el futuro.
En NGINX, ahora estamos poniendo algo de piel en el juego para satisfacer los deseos y necesidades de los desarrolladores en un mundo cada vez más digital y consciente de la seguridad. Nos complace anunciar el proyecto ngx-rust : una nueva forma de escribir módulos NGINX con el lenguaje Rust. ¡Rústáceos, esto es para vosotros!
Los seguidores cercanos de NGINX y nuestro GitHub podrían darse cuenta de que esta no es nuestra primera encarnación de módulos basados en Rust. En los primeros años de Kubernetes y los primeros días de Service Mesh , se manifestó algo de trabajo en torno a Rust, creando las bases para el proyecto ngx-rust.
Originalmente, ngx-rust funcionó como una forma de acelerar el desarrollo de un producto de malla de servicios compatible con Istio con NGINX. Tras el desarrollo del prototipo inicial, este proyecto se mantuvo sin cambios durante muchos años. Durante ese tiempo, muchos miembros de la comunidad bifurcaron el repositorio o crearon proyectos inspirados en los ejemplos de enlaces originales de Rust proporcionados en ngx-rust.
Avanzamos rápidamente y nuestro equipo de F5 Distributed Cloud Bot Defense necesitaba integrar servidores proxy NGINX en sus servicios de protección. Esto requirió construir un nuevo módulo.
También queríamos seguir ampliando nuestro portafolio de Rust al tiempo que mejorábamos la experiencia de los desarrolladores y satisfacíamos las necesidades cambiantes de los clientes. Entonces, aprovechamos nuestros patrocinios de innovación interna y trabajamos con el autor original de ngx-rust para desarrollar un proyecto de enlaces de Rust nuevo y mejorado. Después de una larga pausa, reiniciamos la publicación de cajas ngx-rust con documentación mejorada y mejoras en la ergonomía de la compilación para el uso de la comunidad.
Los módulos son los componentes básicos de NGINX e implementan la mayor parte de su funcionalidad. Los módulos también son la forma más poderosa en que los usuarios de NGINX pueden personalizar esa funcionalidad y crear soporte para casos de uso específicos.
Tradicionalmente, NGINX solo ha admitido módulos escritos en C (como proyecto escrito en C, admitir enlaces de módulos en el lenguaje host fue una elección clara y sencilla). Sin embargo, los avances en la ciencia informática y en la teoría del lenguaje de programación han mejorado los paradigmas anteriores, especialmente con respecto a la seguridad y corrección de la memoria. Esto ha allanado el camino para lenguajes como Rust, que ahora pueden estar disponibles para el desarrollo de módulos NGINX.
Ahora que hemos cubierto parte de la historia de NGINX y Rust, comencemos a construir un módulo. Eres libre de compilar desde la fuente y desarrollar tu módulo localmente, extraer la fuente ngx-rust y ayudar a compilar mejores enlaces, o simplemente extraer el paquete desde crates.io .
El README de ngx-rust cubre las pautas de contribución y los requisitos de compilación local para comenzar. Todavía es temprano y está en su desarrollo inicial, pero nuestro objetivo es mejorar la calidad y las funciones con el apoyo de la comunidad. En este tutorial nos centramos en la creación de un módulo independiente simple. También puedes consultar los ejemplos de ngx-rust para lecciones más complejas.
Las encuadernaciones están organizadas en dos cajas:
nginx-sys
es un paquete que genera enlaces a partir del código fuente de NGINX. El archivo descarga el código fuente de NGINX, las dependencias y utiliza la automatización del código bindgen
para crear los enlaces de la interfaz de función externa (FFI).Las instrucciones a continuación inicializarán un espacio de trabajo esqueleto. Comience creando un directorio de trabajo e inicialice el proyecto Rust:
cd $TU_ARENA_DEV
mkdir ngx-rust-howto
cd ngx-rust-howto
cargo init --lib
A continuación, abra el archivo Cargo.toml y agregue la siguiente sección:
[lib]
tipo-de-caja = ["cdylib"]
[dependencias]
ngx = "0.3.0-beta"
Alternativamente, si desea ver el módulo completo mientras lee, puede clonarlo desde Git:
cd $TU_ARENA_DEV
git clone git@github.com:f5yacobucci/ngx-rust-howto.git
Y con eso, estás listo para comenzar a desarrollar tu primer módulo NGINX Rust. La estructura, la semántica y el enfoque general para construir un módulo no se verán muy diferentes de lo que es necesario cuando se usa C. Por ahora, nos hemos propuesto ofrecer enlaces NGINX en un enfoque iterativo para generar los enlaces, que sean utilizables y estén en manos de los desarrolladores para crear sus ofertas innovadoras. En el futuro, trabajaremos para construir una experiencia de Rust mejor y más idiomática.
Esto significa que el primer paso es construir el módulo junto con las directivas, el contexto y otros aspectos necesarios para su instalación y ejecución en NGINX. El módulo será un controlador simple que puede aceptar o rechazar una solicitud según el método HTTP y creará una nueva directiva que acepta un solo argumento. Discutiremos esto paso a paso, pero puedes consultar el código completo en el repositorio ngx-rust-howto en GitHub.
Nota: Este blog se centra en describir los detalles de Rust, en lugar de cómo crear módulos NGINX en general. Si está interesado en crear otros módulos NGINX, consulte las excelentes discusiones que hay en la comunidad. Estas discusiones también le brindarán una explicación más fundamental de cómo extender NGINX (ver más en la sección Recursos a continuación).
Puede crear su módulo Rust implementando el rasgo HTTPModule
, que define todos los puntos de entrada de NGINX ( postconfiguration
, preconfiguration
, create_main_conf
, etc.). Un escritor de módulos sólo necesita implementar las funciones necesarias para su tarea. Este módulo implementará el método de postconfiguración para instalar su controlador de solicitudes.
Nota: Si no ha clonado el repositorio ngx-rust-howto , puede comenzar a editar el archivo src/lib.rs
creado por cargo init
.
Estructura Módulo;
Impl http::HTTPModule para Módulo {
tipo MainConf = ();
tipo SrvConf = ();
tipo LocConf = ModuleConfig;
unsafe externa "C" función postconfiguración(cf: *mut ngx_conf_t) -> ngx_int_t {
let htcf = http::ngx_http_conf_get_module_main_conf(cf, &ngx_http_core_module);
let h = ngx_array_push(
&mut (*htcf).phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE como usize].handlers, ) como *mut ngx_http_handler_pt;
if h.is_null() {
devuelve core::Status::NGX_ERROR.into(); }
// Establecer un controlador de fase de acceso
*h = Some(howto_access_handler);
core::Status::NGX_OK.into()
}
}
El módulo Rust solo necesita un gancho de postconfiguración
en la fase de acceso NGX_HTTP_ACCESS_PHASE
. Los módulos pueden registrar controladores para varias fases de la solicitud HTTP. Para obtener más información sobre esto, consulte los detalles en la guía de desarrollo .
Verá el controlador de fase howto_access_handler
agregado justo antes de que la función regrese. Volveremos a esto más adelante. Por ahora, solo tenga en cuenta que es la función que realizará la lógica de manejo durante la cadena de solicitud.
Dependiendo del tipo de módulo y sus necesidades, estos son los ganchos de registro disponibles:
preconfiguración
postconfiguración
crear_configuración_principal
configuración principal de inicio
crear_srv_conf
configuración del servidor de fusión
crear_loc_conf
configuración de ubicación de fusión
Ahora es el momento de crear almacenamiento para su módulo. Estos datos incluyen cualquier parámetro de configuración necesario o el estado interno utilizado para procesar solicitudes o alterar el comportamiento. Básicamente, cualquier información que el módulo necesite conservar se puede colocar en estructuras y guardar. Este módulo Rust utiliza una estructura ModuleConfig
en el nivel de configuración de ubicación. El almacenamiento de configuración debe implementar las características Merge y Default
.
Al definir su módulo en el paso anterior, puede establecer los tipos para sus configuraciones principal, de servidor y de ubicación. El módulo Rust que estás desarrollando aquí solo admite ubicaciones, por lo que solo se configura el tipo LocConf
.
Para crear almacenamiento de estado y configuración para su módulo, defina una estructura e implemente el rasgo Merge :
#[derivar(Depurar, Predeterminado)]
struct ModuleConfig {
habilitado: bool,
método: Cadena,
}
impl http::Fusionar para ModuleConfig {
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
if prev.enabled {
self.enabled = true;
}
if self.method.is_empty() {
self.method = String::from(if !prev.method.is_empty() {
&prev.method
} else {
""
});
}
if self.enabled && self.method.is_empty() {
return Err(MergeConfigError::NoValue);
}
Ok(())
}
}
ModuleConfig
almacena un estado activado/desactivado en el campo habilitado, junto con un método de solicitud HTTP. El controlador comprobará este método y permitirá o prohibirá las solicitudes.
Una vez definido el almacenamiento, su módulo puede crear directivas y reglas de configuración para que los usuarios las configuren ellos mismos. NGINX utiliza el tipo ngx_command_t
y una matriz para registrar directivas definidas por el módulo en el sistema central.
A través de los enlaces FFI, los escritores de módulos Rust tienen acceso al tipo ngx_command_t
y pueden registrar directivas como lo harían en C. El módulo ngx-rust-howto
define una directiva howto
que acepta un valor de cadena. Para este caso, definimos un comando, implementamos una función setter y luego (en la siguiente sección) conectamos esos comandos al sistema central. Recuerde finalizar su matriz de comandos con la macro ngx_command_null!
proporcionada.
A continuación se explica cómo crear una directiva simple utilizando comandos NGINX:
#[no_mangle]
mut estático ngx_http_howto_commands: [ngx_command_t; 2] = [
ngx_command_t {
nombre: ngx_string!("howto"),
tipo_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) como ngx_uint_t,
establecido: Algunos(ngx_http_howto_commands_set_method),
conf: NGX_RS_HTTP_LOC_CONF_OFFSET, desplazamiento: 0,
post: std::ptr::null_mut(),
},
ngx_null_command!(),
];
#[no_mangle]
extern "C" fn ngx_http_howto_commands_set_method(
cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
conf: *mut c_void,
) -> *mut c_char {
unsafe {
let conf = &mut *(conf as *mut ModuleConfig);
let args = (*(*cf).args).elts as *mut ngx_str_t;
conf.enabled = true;
conf.method = (*args.add(1)).to_string();
};
std::ptr::null_mut() }
Ahora que tiene una función de registro, un controlador de fase y comandos para la configuración, puede conectar todo y exponer las funciones al sistema central. Cree una estructura estática ngx_module_t
con referencias a sus funciones de registro, controladores de fase y comandos de directiva. Cada módulo debe contener una variable global de tipo ngx_module_t
.
Luego, cree un contexto y un tipo de módulo estático, y expóngalos con la macro ngx_modules!.
En el siguiente ejemplo, puede ver cómo se configuran los comandos
en el campo de comandos y cómo se configura el contexto que hace referencia a las funciones de registro de los módulos en el campo ctx
. Para este módulo, todos los demás campos son valores predeterminados.
#[no_mangle]
módulo_de_cómo_ngx_http_estático_ctx: módulo_ngx_http_t = módulo_ngx_http_t {
preconfiguración: Algún(Módulo::preconfiguración), posconfiguración: Algunos(Módulo::postconfiguración),
crear_configuración_principal: Algún(Módulo::create_main_conf),
init_main_conf: Algún(Módulo::init_main_conf),
crear_srv_conf: Algunos(Módulo::create_srv_conf),
merge_srv_conf: Algún(Módulo::merge_srv_conf),
crear_loc_conf: Algún(Módulo::create_loc_conf),
merge_loc_conf: Algún(Módulo::merge_loc_conf), };
ngx_modules!(ngx_http_howto_module);
#[no_mangle]
pub static mut ngx_http_howto_module: ngx_module_t = ngx_module_t {
ctx_index: ngx_uint_t::valor_máximo(),
index: ngx_uint_t::valor_máximo(),
name: std::ptr::null_mut(),
spare0: 0,
repuesto1: 0,
versión: nginx_version como ngx_uint_t,
firma: NGX_RS_MODULE_SIGNATURE.as_ptr() como *const c_char,
ctx: &ngx_http_howto_module_ctx como *const _ como *mut _,
comandos: inseguro { &ngx_http_howto_commands[0] como *const _ como *mut _ },
tipo_: MÓDULO HTTP NGX como ngx_uint_t,
init_master: Ninguno,
módulo_init: Ninguno, proceso_de_inicio: Ninguno,
init_thread: Ninguno,
salir_hilo: Ninguno, proceso_de_salida: Ninguno,
exit_master: Ninguno,
spare_hook0: 0,
spare_hook1: 0,
spare_hook2: 0,
spare_hook3: 0,
spare_hook4: 0,
spare_hook5: 0,
spare_hook6: 0,
spare_hook7: 0, };
Después de esto, prácticamente habrás completado los pasos necesarios para configurar y registrar un nuevo módulo Rust. Dicho esto, todavía es necesario implementar el controlador de fase (howto_access_handler)
que se configuró en el gancho posterior a la configuración
.
Los controladores se llaman para cada solicitud entrante y realizan la mayor parte del trabajo de su módulo. Los controladores de solicitudes han sido el foco del equipo ngx-rust y son donde se han realizado la mayoría de las mejoras ergonómicas iniciales. Si bien los pasos de configuración anteriores requieren escribir Rust en un estilo similar a C, ngx-rust proporciona más conveniencia y utilidades para los controladores de solicitudes.
Como se ve en el ejemplo a continuación, ngx-rust proporciona la macro http_request_handler!
para aceptar un cierre de Rust llamado con una instancia de Solicitud
. También proporciona utilidades para obtener configuración y variables, establecer esas variables y acceder a la memoria, otras primitivas NGINX y API.
Para iniciar un procedimiento de controlador, invoque la macro y proporcione su lógica comercial como un cierre de Rust. Para el módulo ngx-rust-howto, verifique el método de la solicitud para permitir que la solicitud continúe procesándose.
http_request_handler!(howto_access_handler, |request: &mut http::Request| {
let co = unsafe { request.get_module_loc_conf::(&ngx_http_howto_module) };
let co = co.expect("La configuración del módulo es ninguna");
ngx_log_debug_http!(request, "El módulo howto habilitado fue llamado");
match co.enabled {
true => {
let method = request.method();
if method.as_str() == co.method {
return core::Status::NGX_OK;
}
http::HTTPStatus::FORBIDDEN.into()
}
false => core::Status::NGX_OK,
}
});
¡Con esto habrás completado tu primer módulo de Rust!
El repositorio ngx-rust-howto en GitHub contiene un archivo de configuración NGINX en el directorio conf. También puedes compilar (con cargo build
), añadir el binario del módulo a la directiva load_module
en un archivo nginx.conf local y ejecutarlo con una instancia de NGINX. Para este tutorial, usamos NGINX v1.23.3, la versión NGINX_VERSION predeterminada compatible con ngx-rust. Al crear y ejecutar módulos dinámicos, asegúrese de utilizar la misma NGINX_VERSION
para las compilaciones de ngx-rust que la instancia NGINX que está ejecutando en su máquina.
NGINX es un sistema de software maduro con años de características y casos de uso incorporados. Es un proxy capaz, un equilibrador de carga y un servidor web de clase mundial. Su presencia en el mercado es segura durante los próximos años, lo que alimenta nuestra motivación para desarrollar sus capacidades y brindar a nuestros usuarios nuevos métodos para interactuar con él. Con la popularidad de Rust entre los desarrolladores y sus restricciones de seguridad mejoradas, estamos entusiasmados de ofrecer la opción de usar Rust junto con el mejor servidor web del mundo.
Sin embargo, la madurez de NGINX y su ecosistema rico en funciones crean una gran superficie de API y ngx-rust apenas ha arañado la superficie. El proyecto tiene como objetivo mejorar y expandirse agregando interfaces Rust más idiomáticas, creando módulos de referencia adicionales y mejorando la ergonomía de los módulos de escritura.
¡Aquí es donde entras tú! El proyecto ngx-rust está abierto a todos y está disponible en GitHub . Estamos ansiosos por trabajar con la comunidad NGINX para seguir mejorando las capacidades y la facilidad de uso del módulo. ¡Pruébalo y experimenta tú mismo con las fijaciones! Y, por favor, comuníquese con nosotros, presente problemas o relaciones públicas e interactúe con nosotros en el canal Slack de la comunidad NGINX .
"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.