비교적 짧은 역사에도 불구하고 프로그래밍 언어인 Rust는 풍부하고 성숙한 에코시스템과 함께 뛰어난 찬사를 받았습니다. Rust와 Cargo(빌드 시스템, 툴체인 인터페이스, 패키지 관리자)는 모두 업계에서 <데이터-dl-uid="29">찬사를 받고 있는 기술이며, <데이터-dl-uid="30">RedMonk의 프로그래밍 언어 순위에서 상위 20개 언어 중 안정적인 위치를 차지하고 있습니다. 또한, Rust를 채택한 프로젝트에서는 안정성 및 보안 관련 프로그래밍 오류가 개선되는 경우가 많습니다(예를 들어, Android 개발자들은 강력한 개선 사례에 대해 이야기합니다).
F5는 한동안 Rust와 그 커뮤니티를 둘러싼 이러한 발전 상황을 흥미롭게 지켜봐 왔습니다. 언어에 대한 적극적인 지지와 툴체인, 그리고 앞으로의 채택을 주목해 왔습니다.
이제 점점 더 디지털화되고 보안에 민감한 세상에서 개발자의 요구와 필요를 충족시키기 위해 NGINX에서 몇 가지 스킨을 적용하고 있습니다. Rust 언어로 NGINX 모듈을 작성하는 새로운 방법인 ngx-rust 프로젝트를 발표하게 되어 기쁘게 생각합니다. 러스트족 여러분, 여러분을 위한 것입니다!
NGINX와 Rust의 간략한 역사
NGINX와 GitHub의 열성적인 팔로워라면 이것이 Rust 기반 모듈의 첫 번째 화신이 아니라는 사실을 알 수 있습니다. Kubernetes와 서비스 메시의 초창기에는 일부 작업이 Rust를 중심으로 나타나면서 ngx-rust 프로젝트의 토대를 마련했습니다.
원래 ngx-rust는 NGINX로 Istio 호환 서비스 메시 제품 개발을 가속화하는 역할을 했습니다. 초기 프로토타입을 개발한 후 이 프로젝트는 수년 동안 변경되지 않은 채로 방치되었습니다. 그 기간 동안 많은 커뮤니티 구성원이 리포지토리를 포크하거나 ngx-rust에서 제공된 원본 Rust 바인딩 예제에서 영감을 받아 프로젝트를 만들었습니다.
급기야 F5 Distributed Cloud Bot Defense 팀은 NGINX 프록시를 보호 서비스에 통합해야 했습니다. 이를 위해서는 새로운 모듈을 구축해야 했습니다.
또한 개발자 경험을 개선하고 고객의 진화하는 요구사항을 충족하는 동시에 Rust 포트폴리오를 계속 확장하고자 했습니다. 그래서 내부 혁신 후원을 활용하고 원조 ngx-rust 작성자와 협력하여 새롭고 개선된 Rust 바인딩 프로젝트를 개발했습니다. 오랜 중단 끝에 커뮤니티 사용을 위한 인체공학적 설계를 위해 향상된 문서와 개선된 기능으로 ngx-rust 상자 게시를 다시 시작하게 되었습니다.
NGINX에 어떤 의미가 있나요?
모듈은 대부분의 기능을 구현하는 NGINX의 핵심 빌딩 블록입니다. 또한 모듈은 NGINX 사용자가 해당 기능을 사용자 정의하고 특정 사용 사례에 대한 지원을 구축할 수 있는 가장 강력한 방법이기도 합니다.
NGINX는 전통적으로 C로 작성된 모듈만 지원했습니다(C로 작성된 프로젝트의 경우 호스트 언어로 모듈 바인딩을 지원하는 것이 명확하고 쉬운 선택이었습니다). 그러나 컴퓨터 과학과 프로그래밍 언어 이론의 발전으로 특히 메모리 안전성과 정확성 측면에서 과거의 패러다임이 개선되었습니다. 이에 따라 이제 Rust와 같은 언어를 NGINX 모듈 개발에 사용할 수 있는 길이 열렸습니다.
ngx-rust를 시작하는 방법
이제 NGINX와 Rust의 역사에 대해 알아봤으니 모듈을 만들어 보겠습니다. 소스에서 빌드하여 로컬에서 모듈을 개발하거나, ngx-rust 소스를 가져와 더 나은 바인딩을 구축하거나, crates.io에서 크레이트를 가져올 수 있습니다.
ngx-rust README에서는 시작을 위한 기여 가이드라인과 로컬 빌드 요구 사항을 다루고 있습니다. 아직 초기 개발 단계이지만 커뮤니티의 지원을 통해 품질과 기능을 개선하고자 합니다. 이 튜토리얼에서는 간단한 독립 모듈을 만드는 데 중점을 둡니다. 좀 더 복잡한 예제를 보려면 ngx-rust 예제를 참조할 수도 있습니다.
바인딩은 두 개의 상자로 구성되어 있습니다:
- nginx-sys는 NGINX 소스 코드에서 바인딩을 생성하는 크레이트입니다. 이 파일은 NGINX 소스 코드와 종속성을 다운로드하고 bindgen 코드 자동화를 사용하여 외부 함수 인터페이스(FFI) 바인딩을 생성합니다.
- ngx is the main crate that implements Rust glue code, APIs, and re-exports nginx-sys. Module writers import and interact with NGINX through these symbols while the re-export of nginx-sys removes the need to import it explicitly.
The instructions below will initialize a skeleton workspace. Begin by creating a working directory and initialize the Rust project:
cd $YOUR_DEV_ARENA mkdir ngx-rust-howto cd ngx-rust-howto cargo init --lib
Next, open the Cargo.toml file and add the following section:
[lib] crate-type = ["cdylib"] [dependencies] ngx = "0.3.0-beta"
Alternatively, if you want to see the completed module while reading along, it can be cloned from Git:
cd $YOUR_DEV_ARENA git clone git@github.com:f5yacobucci/ngx-rust-howto.git
이제 첫 번째 NGINX Rust 모듈 개발을 시작할 준비가 되었습니다. 모듈을 구성하는 구조, 의미론 및 일반적인 접근 방식은 C를 사용할 때 필요한 것과 크게 다르지 않을 것입니다. 현재로서는 개발자가 바인딩을 생성하고 사용 가능하며 창의적인 제품을 만들 수 있도록 반복적인 접근 방식으로 NGINX 바인딩을 제공하기로 결정했습니다. 앞으로는 더 나은, 더 관용적인 Rust 환경을 구축하기 위해 노력할 것입니다.
즉, 첫 번째 단계는 NGINX에서 설치하고 실행하는 데 필요한 모든 지시어, 컨텍스트 및 기타 측면과 함께 모듈을 구성하는 것입니다. 모듈은 HTTP 메서드를 기반으로 요청을 수락하거나 거부할 수 있는 간단한 핸들러가 될 것이며, 단일 인수를 허용하는 새 지시문을 생성할 것입니다. 이에 대해서는 단계별로 설명하겠지만 전체 코드는 GitHub의 ngx-rust-howto 리포지토리에서 참조할 수 있습니다.
참고: 이 블로그에서는 일반적인 NGINX 모듈 빌드 방법보다는 Rust의 세부 사항을 설명하는 데 중점을 두고 있습니다. 다른 NGINX 모듈을 구축하는 데 관심이 있으시다면 커뮤니티에 있는 많은 훌륭한 토론을 참조하시기 바랍니다. 이러한 토론을 통해 NGINX를 확장하는 방법에 대한 보다 근본적인 설명도 얻을 수 있습니다(아래 리소스 섹션에서 자세히 참조하세요).
Module Registration
모든 NGINX 진입점을 정의하는 HTTPModule 특성을 구현하여 Rust 모듈을 만들 수 있습니다(postconfiguration, preconfiguration, create_main_conf 등). 모듈 작성자는 해당 작업에 필요한 함수만 구현하면 됩니다. 이 모듈은 요청 핸들러를 설치하기 위해 사후 구성 방법을 구현합니다.
주: ngx-rust-howto 레포를 복제하지 않은 경우 src/lib.rs로 만든 cargo init 파일 편집을 시작할 수 있습니다.
struct Module;
impl http::HTTPModule for Module {
type MainConf = ();
type SrvConf = ();
type LocConf = ModuleConfig;
unsafe extern "C" fn postconfiguration(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 as usize].handlers,
) as *mut ngx_http_handler_pt;
if h.is_null() {
return core::Status::NGX_ERROR.into();
}
// set an Access phase handler
*h = Some(howto_access_handler);
core::Status::NGX_OK.into()
}
}
Rust 모듈은 액세스 단계 postconfiguration에서 NGX_HTTP_ACCESS_PHASE 후크만 필요합니다. 모듈은 HTTP 요청의 다양한 단계에 대한 핸들러를 등록할 수 있습니다. 이에 대한 자세한 내용은 개발 가이드에서 자세한 내용을 참조하세요.
함수가 반환되기 직전에 페이즈 핸들러 howto_access_handler가 추가된 것을 볼 수 있습니다. 이 부분은 나중에 다시 설명하겠습니다. 지금은 요청 체인 중에 처리 로직을 수행하는 함수라는 점만 기억하세요.
모듈 유형과 필요에 따라 사용 가능한 등록 훅은 다음과 같습니다:
- 사전 구성
- postconfiguration
- create_main_conf
- init_main_conf
- create_srv_conf
- merge_srv_conf
- CREATE_LOC_CONF
- merge_loc_conf
구성 상태
이제 모듈을 위한 스토리지를 만들 차례입니다. 이 데이터에는 필요한 모든 구성 매개변수 또는 요청을 처리하거나 동작을 변경하는 데 사용되는 내부 상태가 포함됩니다. 기본적으로 모듈이 유지해야 하는 정보는 무엇이든 구조체에 넣고 저장할 수 있습니다. 이 Rust 모듈은 위치 구성 수준에서 ModuleConfig 구조를 사용합니다. 구성 저장소는 병합 및 기본값 특성을 구현해야 합니다.
위 단계에서 모듈을 정의할 때 메인, 서버 및 위치 구성에 대한 유형을 설정할 수 있습니다. 여기서 개발 중인 Rust 모듈은 위치만 지원하므로 LocConf 유형만 설정되어 있습니다.
모듈에 대한 상태 및 구성 저장소를 만들려면 구조를 정의하고 Merge 특성을 구현하세요:
#[derive(Debug, Default)]
struct ModuleConfig {
enabled: bool,
method: String,
}
impl http::Merge for 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는 HTTP 요청 메서드와 함께 활성화/비활성화 상태를 활성화 필드에 저장합니다. 핸들러는 이 메서드와 비교하여 요청을 허용하거나 금지합니다.
스토리지가 정의되면 모듈은 사용자가 직접 설정할 수 있는 지시어와 구성 규칙을 생성할 수 있습니다. NGINX는 ngx_command_t 유형과 배열을 사용하여 모듈 정의 지시어를 코어 시스템에 등록합니다.
Rust 모듈 작성자는 FFI 바인딩을 통해 ngx_command_t 유형에 액세스하고 C에서와 마찬가지로 지시어를 등록할 수 있습니다. ngx-rust-howto 모듈은 문자열 값을 받아들이는 howto 지시어를 정의합니다. 이 사례에서는 하나의 명령을 정의하고 세터 함수를 구현한 다음 (다음 섹션에서) 해당 명령을 코어 시스템에 연결합니다. 제공된 ngx_command_null! 매크로로 명령 배열을 종료하는 것을 잊지 마세요.
다음은 NGINX 명령을 사용하여 간단한 지시문을 만드는 방법입니다:
#[no_mangle]
static mut ngx_http_howto_commands: [ngx_command_t; 2] = [
ngx_command_t {
name: ngx_string!("howto"),
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
set: Some(ngx_http_howto_commands_set_method),
conf: NGX_RS_HTTP_LOC_CONF_OFFSET,
offset: 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()
}
모듈에 후킹하기
이제 등록 함수, 페이즈 핸들러, 구성용 명령어를 준비했으므로 모든 것을 함께 후킹하여 코어 시스템에 함수를 노출할 수 있습니다. 등록 함수, 페이즈 핸들러 및 지시어 명령에 대한 참조가 포함된 정적 ngx_module_t 구조를 만듭니다. 모든 모듈에는 ngx_module_t 유형의 전역 변수가 포함되어야 합니다.
그런 다음 컨텍스트 및 정적 모듈 유형을 생성하고 ngx_modules! 매크로를 사용하여 노출합니다. 아래 예시에서는 명령이 commands 필드에 설정되고 모듈 등록 함수를 참조하는 컨텍스트가 ctx 필드에 설정되는 방식을 볼 수 있습니다. 이 모듈의 경우 다른 모든 필드는 사실상 기본값입니다.
#[no_mangle]
static ngx_http_howto_module_ctx: ngx_http_module_t = ngx_http_module_t {
preconfiguration: Some(Module::preconfiguration),
postconfiguration: Some(Module::postconfiguration),
create_main_conf: Some(Module::create_main_conf),
init_main_conf: Some(Module::init_main_conf),
create_srv_conf: Some(Module::create_srv_conf),
merge_srv_conf: Some(Module::merge_srv_conf),
create_loc_conf: Some(Module::create_loc_conf),
merge_loc_conf: Some(Module::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::max_value(),
index: ngx_uint_t::max_value(),
name: std::ptr::null_mut(),
spare0: 0,
spare1: 0,
version: nginx_version as ngx_uint_t,
signature: NGX_RS_MODULE_SIGNATURE.as_ptr() as *const c_char,
ctx: &ngx_http_howto_module_ctx as *const _ as *mut _,
commands: unsafe { &ngx_http_howto_commands[0] as *const _ as *mut _ },
type_: NGX_HTTP_MODULE as ngx_uint_t,
init_master: None,
init_module: None,
init_process: None,
init_thread: None,
exit_thread: None,
exit_process: None,
exit_master: None,
spare_hook0: 0,
spare_hook1: 0,
spare_hook2: 0,
spare_hook3: 0,
spare_hook4: 0,
spare_hook5: 0,
spare_hook6: 0,
spare_hook7: 0,
};
After this, you’ve practically completed the steps necessary to set up and register a new Rust module. That said, you still need to implement the phase handler (howto_access_handler) that was set in the postconfiguration hook.
핸들러는 들어오는 각 요청에 대해 호출되며 모듈의 대부분의 작업을 수행합니다. 요청 핸들러는 ngx-rust 팀이 중점을 두고 있으며, 초기 인체공학적 개선의 대부분이 이루어진 부분입니다. 이전 설정 단계에서는 Rust를 C와 같은 스타일로 작성해야 했지만, ngx-rust는 요청 핸들러에 더 많은 편의성과 유틸리티를 제공합니다.
아래 예제에서 볼 수 있듯이, ngx-rust는 요청 인스턴스와 함께 호출된 Rust 클로저를 수락하는 매크로 http_request_handler! 를 제공합니다. 또한 구성 및 변수를 가져오고, 해당 변수를 설정하고, 메모리, 기타 NGINX 프리미티브 및 API에 액세스하기 위한 유틸리티도 제공합니다.
핸들러 프로시저를 시작하려면 매크로를 호출하고 비즈니스 로직을 Rust 클로저로 제공하세요. ngx-rust-howto 모듈의 경우 요청이 계속 처리될 수 있도록 요청의 메서드를 확인합니다.
핸들러는 들어오는 각 요청에 대해 호출되며 모듈의 대부분의 작업을 수행합니다. 요청 핸들러는 ngx-rust 팀이 중점을 두고 있으며, 초기 인체공학적 개선의 대부분이 이루어진 부분입니다. 이전 설정 단계에서는 Rust를 C와 같은 스타일로 작성해야 했지만, ngx-rust는 요청 핸들러에 더 많은 편의성과 유틸리티를 제공합니다.
아래 예제에서 볼 수 있듯이, ngx-rust는 요청 인스턴스와 함께 호출된 Rust 클로저를 수락하는 매크로 http_request_handler! 를 제공합니다. 또한 구성 및 변수를 가져오고, 해당 변수를 설정하고, 메모리, 기타 NGINX 프리미티브 및 API에 액세스하기 위한 유틸리티도 제공합니다.
핸들러 프로시저를 시작하려면 매크로를 호출하고 비즈니스 로직을 Rust 클로저로 제공하세요. ngx-rust-howto 모듈의 경우 요청이 계속 처리될 수 있도록 요청의 메서드를 확인합니다.
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("module config is none");
ngx_log_debug_http!(request, "howto module enabled called");
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,
}
});
이것으로 첫 번째 Rust 모듈을 완성했습니다!
GitHub의 ngx-rust-howto 리포지토리에는 conf 디렉터리에 NGINX 구성 파일이 포함되어 있습니다. 또한 빌드(cargo build 사용)하고, 로컬 nginx.conf의 load_module 지시어에 모듈 바이너리를 추가하고, NGINX 인스턴스를 사용하여 실행할 수 있습니다. 이 튜토리얼을 작성할 때는 ngx-rust에서 지원하는 기본 NGINX_VERSION인 NGINX v1.23.3을 사용했습니다. 동적 모듈을 빌드하고 실행할 때는 컴퓨터에서 실행 중인 NGINX 인스턴스와 동일한 NGINX_VERSION를 ngx-rust 빌드에 사용해야 합니다.
결론
NGINX는 수년간의 기능과 사용 사례가 내장된 성숙한 소프트웨어 시스템입니다. 유능한 프록시, 로드 밸런서이자 세계적 수준의 웹 서버입니다. 앞으로 몇 년 동안 시장에서의 입지가 확실하기 때문에, 그 기능을 기반으로 사용자에게 새로운 상호 작용 방법을 제공하고자 하는 의욕이 생겼습니다. 개발자들 사이에서 Rust의 인기가 높아지고 안전성이 개선됨에 따라 세계 최고의 웹 서버와 함께 Rust를 사용할 수 있는 옵션을 제공하게 되어 매우 기쁘게 생각합니다.
하지만 NGINX의 성숙도와 풍부한 기능의 에코시스템으로 인해 API 표면적이 넓어졌고, ngx-rust는 이제 시작에 불과합니다. 이 프로젝트는 더 많은 관용적 Rust 인터페이스 추가, 추가 참조 모듈 구축, 모듈 작성의 인체공학적 개선을 통해 개선하고 확장하는 것을 목표로 합니다.
바로 여기에 여러분이 필요합니다! ngx-rust 프로젝트는 누구에게나 열려 있으며 GitHub에서 사용할 수 있습니다. 저희는 모듈의 기능과 사용 편의성을 지속적으로 개선하기 위해 NGINX 커뮤니티와 협력하고 있습니다. 직접 확인하고 바인딩을 실험해 보세요!
위 내용와 같이 NGINX Plus를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요
비교적 짧은 역사에도 불구하고 프로그래밍 언어인 Rust는 풍부하고 성숙한 에코시스템과 함께 뛰어난 찬사를 받았습니다. Rust와 Cargo(빌드 시스템, 툴체인 인터페이스, 패키지 관리자)는 모두 업계에서 <데이터-dl-uid="29">찬사를 받고 있는 기술이며, <데이터-dl-uid="30">RedMonk의 프로그래밍 언어 순위에서 상위 20개 언어 중 안정적인 위치를 차지하고 있습니다. 또한, Rust를 채택한 프로젝트에서는 안정성 및 보안 관련 프로그래밍 오류가 개선되는 경우가 많습니다(예를 들어, Android 개발자들은 강력한 개선 사례에 대해 이야기합니다).
F5는 한동안 Rust와 그 커뮤니티를 둘러싼 이러한 발전 상황을 흥미롭게 지켜봐 왔습니다. 언어에 대한 적극적인 지지와 툴체인, 그리고 앞으로의 채택을 주목해 왔습니다.
이제 점점 더 디지털화되고 보안에 민감한 세상에서 개발자의 요구와 필요를 충족시키기 위해 NGINX에서 몇 가지 스킨을 적용하고 있습니다. Rust 언어로 NGINX 모듈을 작성하는 새로운 방법인 ngx-rust 프로젝트를 발표하게 되어 기쁘게 생각합니다. 러스트족 여러분, 여러분을 위한 것입니다!
NGINX와 Rust의 간략한 역사
NGINX와 GitHub의 열성적인 팔로워라면 이것이 Rust 기반 모듈의 첫 번째 화신이 아니라는 사실을 알 수 있습니다. Kubernetes와 서비스 메시의 초창기에는 일부 작업이 Rust를 중심으로 나타나면서 ngx-rust 프로젝트의 토대를 마련했습니다.
원래 ngx-rust는 NGINX로 Istio 호환 서비스 메시 제품 개발을 가속화하는 역할을 했습니다. 초기 프로토타입을 개발한 후 이 프로젝트는 수년 동안 변경되지 않은 채로 방치되었습니다. 그 기간 동안 많은 커뮤니티 구성원이 리포지토리를 포크하거나 ngx-rust에서 제공된 원본 Rust 바인딩 예제에서 영감을 받아 프로젝트를 만들었습니다.
급기야 F5 Distributed Cloud Bot Defense 팀은 NGINX 프록시를 보호 서비스에 통합해야 했습니다. 이를 위해서는 새로운 모듈을 구축해야 했습니다.
또한 개발자 경험을 개선하고 고객의 진화하는 요구사항을 충족하는 동시에 Rust 포트폴리오를 계속 확장하고자 했습니다. 그래서 내부 혁신 후원을 활용하고 원조 ngx-rust 작성자와 협력하여 새롭고 개선된 Rust 바인딩 프로젝트를 개발했습니다. 오랜 중단 끝에 커뮤니티 사용을 위한 인체공학적 설계를 위해 향상된 문서와 개선된 기능으로 ngx-rust 상자 게시를 다시 시작하게 되었습니다.
NGINX에 어떤 의미가 있나요?
모듈은 대부분의 기능을 구현하는 NGINX의 핵심 빌딩 블록입니다. 또한 모듈은 NGINX 사용자가 해당 기능을 사용자 정의하고 특정 사용 사례에 대한 지원을 구축할 수 있는 가장 강력한 방법이기도 합니다.
NGINX는 전통적으로 C로 작성된 모듈만 지원했습니다(C로 작성된 프로젝트의 경우 호스트 언어로 모듈 바인딩을 지원하는 것이 명확하고 쉬운 선택이었습니다). 그러나 컴퓨터 과학과 프로그래밍 언어 이론의 발전으로 특히 메모리 안전성과 정확성 측면에서 과거의 패러다임이 개선되었습니다. 이에 따라 이제 Rust와 같은 언어를 NGINX 모듈 개발에 사용할 수 있는 길이 열렸습니다.
ngx-rust를 시작하는 방법
이제 NGINX와 Rust의 역사에 대해 알아봤으니 모듈을 만들어 보겠습니다. 소스에서 빌드하여 로컬에서 모듈을 개발하거나, ngx-rust 소스를 가져와 더 나은 바인딩을 구축하거나, crates.io에서 크레이트를 가져올 수 있습니다.
ngx-rust README에서는 시작을 위한 기여 가이드라인과 로컬 빌드 요구 사항을 다루고 있습니다. 아직 초기 개발 단계이지만 커뮤니티의 지원을 통해 품질과 기능을 개선하고자 합니다. 이 튜토리얼에서는 간단한 독립 모듈을 만드는 데 중점을 둡니다. 좀 더 복잡한 예제를 보려면 ngx-rust 예제를 참조할 수도 있습니다.
바인딩은 두 개의 상자로 구성되어 있습니다:
The instructions below will initialize a skeleton workspace. Begin by creating a working directory and initialize the Rust project:
cd $YOUR_DEV_ARENA mkdir ngx-rust-howto cd ngx-rust-howto cargo init --lib
Next, open the Cargo.toml file and add the following section:
[lib] crate-type = ["cdylib"] [dependencies] ngx = "0.3.0-beta"
Alternatively, if you want to see the completed module while reading along, it can be cloned from Git:
cd $YOUR_DEV_ARENA git clone git@github.com:f5yacobucci/ngx-rust-howto.git
이제 첫 번째 NGINX Rust 모듈 개발을 시작할 준비가 되었습니다. 모듈을 구성하는 구조, 의미론 및 일반적인 접근 방식은 C를 사용할 때 필요한 것과 크게 다르지 않을 것입니다. 현재로서는 개발자가 바인딩을 생성하고 사용 가능하며 창의적인 제품을 만들 수 있도록 반복적인 접근 방식으로 NGINX 바인딩을 제공하기로 결정했습니다. 앞으로는 더 나은, 더 관용적인 Rust 환경을 구축하기 위해 노력할 것입니다.
즉, 첫 번째 단계는 NGINX에서 설치하고 실행하는 데 필요한 모든 지시어, 컨텍스트 및 기타 측면과 함께 모듈을 구성하는 것입니다. 모듈은 HTTP 메서드를 기반으로 요청을 수락하거나 거부할 수 있는 간단한 핸들러가 될 것이며, 단일 인수를 허용하는 새 지시문을 생성할 것입니다. 이에 대해서는 단계별로 설명하겠지만 전체 코드는 GitHub의 ngx-rust-howto 리포지토리에서 참조할 수 있습니다.
참고: 이 블로그에서는 일반적인 NGINX 모듈 빌드 방법보다는 Rust의 세부 사항을 설명하는 데 중점을 두고 있습니다. 다른 NGINX 모듈을 구축하는 데 관심이 있으시다면 커뮤니티에 있는 많은 훌륭한 토론을 참조하시기 바랍니다. 이러한 토론을 통해 NGINX를 확장하는 방법에 대한 보다 근본적인 설명도 얻을 수 있습니다(아래 리소스 섹션에서 자세히 참조하세요).
Module Registration
모든 NGINX 진입점을 정의하는 HTTPModule 특성을 구현하여 Rust 모듈을 만들 수 있습니다(postconfiguration, preconfiguration, create_main_conf 등). 모듈 작성자는 해당 작업에 필요한 함수만 구현하면 됩니다. 이 모듈은 요청 핸들러를 설치하기 위해 사후 구성 방법을 구현합니다.
주: ngx-rust-howto 레포를 복제하지 않은 경우 src/lib.rs로 만든 cargo init 파일 편집을 시작할 수 있습니다.
struct Module; impl http::HTTPModule for Module { type MainConf = (); type SrvConf = (); type LocConf = ModuleConfig; unsafe extern "C" fn postconfiguration(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 as usize].handlers, ) as *mut ngx_http_handler_pt; if h.is_null() { return core::Status::NGX_ERROR.into(); } // set an Access phase handler *h = Some(howto_access_handler); core::Status::NGX_OK.into() } }
Rust 모듈은 액세스 단계 postconfiguration에서 NGX_HTTP_ACCESS_PHASE 후크만 필요합니다. 모듈은 HTTP 요청의 다양한 단계에 대한 핸들러를 등록할 수 있습니다. 이에 대한 자세한 내용은 개발 가이드에서 자세한 내용을 참조하세요.
함수가 반환되기 직전에 페이즈 핸들러 howto_access_handler가 추가된 것을 볼 수 있습니다. 이 부분은 나중에 다시 설명하겠습니다. 지금은 요청 체인 중에 처리 로직을 수행하는 함수라는 점만 기억하세요.
모듈 유형과 필요에 따라 사용 가능한 등록 훅은 다음과 같습니다:
구성 상태
이제 모듈을 위한 스토리지를 만들 차례입니다. 이 데이터에는 필요한 모든 구성 매개변수 또는 요청을 처리하거나 동작을 변경하는 데 사용되는 내부 상태가 포함됩니다. 기본적으로 모듈이 유지해야 하는 정보는 무엇이든 구조체에 넣고 저장할 수 있습니다. 이 Rust 모듈은 위치 구성 수준에서 ModuleConfig 구조를 사용합니다. 구성 저장소는 병합 및 기본값 특성을 구현해야 합니다.
위 단계에서 모듈을 정의할 때 메인, 서버 및 위치 구성에 대한 유형을 설정할 수 있습니다. 여기서 개발 중인 Rust 모듈은 위치만 지원하므로 LocConf 유형만 설정되어 있습니다.
모듈에 대한 상태 및 구성 저장소를 만들려면 구조를 정의하고 Merge 특성을 구현하세요:
#[derive(Debug, Default)] struct ModuleConfig { enabled: bool, method: String, } impl http::Merge for 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는 HTTP 요청 메서드와 함께 활성화/비활성화 상태를 활성화 필드에 저장합니다. 핸들러는 이 메서드와 비교하여 요청을 허용하거나 금지합니다.
스토리지가 정의되면 모듈은 사용자가 직접 설정할 수 있는 지시어와 구성 규칙을 생성할 수 있습니다. NGINX는 ngx_command_t 유형과 배열을 사용하여 모듈 정의 지시어를 코어 시스템에 등록합니다.
Rust 모듈 작성자는 FFI 바인딩을 통해 ngx_command_t 유형에 액세스하고 C에서와 마찬가지로 지시어를 등록할 수 있습니다. ngx-rust-howto 모듈은 문자열 값을 받아들이는 howto 지시어를 정의합니다. 이 사례에서는 하나의 명령을 정의하고 세터 함수를 구현한 다음 (다음 섹션에서) 해당 명령을 코어 시스템에 연결합니다. 제공된 ngx_command_null! 매크로로 명령 배열을 종료하는 것을 잊지 마세요.
다음은 NGINX 명령을 사용하여 간단한 지시문을 만드는 방법입니다:
#[no_mangle] static mut ngx_http_howto_commands: [ngx_command_t; 2] = [ ngx_command_t { name: ngx_string!("howto"), type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t, set: Some(ngx_http_howto_commands_set_method), conf: NGX_RS_HTTP_LOC_CONF_OFFSET, offset: 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() }
모듈에 후킹하기
이제 등록 함수, 페이즈 핸들러, 구성용 명령어를 준비했으므로 모든 것을 함께 후킹하여 코어 시스템에 함수를 노출할 수 있습니다. 등록 함수, 페이즈 핸들러 및 지시어 명령에 대한 참조가 포함된 정적 ngx_module_t 구조를 만듭니다. 모든 모듈에는 ngx_module_t 유형의 전역 변수가 포함되어야 합니다.
그런 다음 컨텍스트 및 정적 모듈 유형을 생성하고 ngx_modules! 매크로를 사용하여 노출합니다. 아래 예시에서는 명령이 commands 필드에 설정되고 모듈 등록 함수를 참조하는 컨텍스트가 ctx 필드에 설정되는 방식을 볼 수 있습니다. 이 모듈의 경우 다른 모든 필드는 사실상 기본값입니다.
#[no_mangle] static ngx_http_howto_module_ctx: ngx_http_module_t = ngx_http_module_t { preconfiguration: Some(Module::preconfiguration), postconfiguration: Some(Module::postconfiguration), create_main_conf: Some(Module::create_main_conf), init_main_conf: Some(Module::init_main_conf), create_srv_conf: Some(Module::create_srv_conf), merge_srv_conf: Some(Module::merge_srv_conf), create_loc_conf: Some(Module::create_loc_conf), merge_loc_conf: Some(Module::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::max_value(), index: ngx_uint_t::max_value(), name: std::ptr::null_mut(), spare0: 0, spare1: 0, version: nginx_version as ngx_uint_t, signature: NGX_RS_MODULE_SIGNATURE.as_ptr() as *const c_char, ctx: &ngx_http_howto_module_ctx as *const _ as *mut _, commands: unsafe { &ngx_http_howto_commands[0] as *const _ as *mut _ }, type_: NGX_HTTP_MODULE as ngx_uint_t, init_master: None, init_module: None, init_process: None, init_thread: None, exit_thread: None, exit_process: None, exit_master: None, spare_hook0: 0, spare_hook1: 0, spare_hook2: 0, spare_hook3: 0, spare_hook4: 0, spare_hook5: 0, spare_hook6: 0, spare_hook7: 0, };
After this, you’ve practically completed the steps necessary to set up and register a new Rust module. That said, you still need to implement the phase handler (howto_access_handler) that was set in the postconfiguration hook.
핸들러는 들어오는 각 요청에 대해 호출되며 모듈의 대부분의 작업을 수행합니다. 요청 핸들러는 ngx-rust 팀이 중점을 두고 있으며, 초기 인체공학적 개선의 대부분이 이루어진 부분입니다. 이전 설정 단계에서는 Rust를 C와 같은 스타일로 작성해야 했지만, ngx-rust는 요청 핸들러에 더 많은 편의성과 유틸리티를 제공합니다.
아래 예제에서 볼 수 있듯이, ngx-rust는 요청 인스턴스와 함께 호출된 Rust 클로저를 수락하는 매크로 http_request_handler! 를 제공합니다. 또한 구성 및 변수를 가져오고, 해당 변수를 설정하고, 메모리, 기타 NGINX 프리미티브 및 API에 액세스하기 위한 유틸리티도 제공합니다.
핸들러 프로시저를 시작하려면 매크로를 호출하고 비즈니스 로직을 Rust 클로저로 제공하세요. ngx-rust-howto 모듈의 경우 요청이 계속 처리될 수 있도록 요청의 메서드를 확인합니다.
핸들러는 들어오는 각 요청에 대해 호출되며 모듈의 대부분의 작업을 수행합니다. 요청 핸들러는 ngx-rust 팀이 중점을 두고 있으며, 초기 인체공학적 개선의 대부분이 이루어진 부분입니다. 이전 설정 단계에서는 Rust를 C와 같은 스타일로 작성해야 했지만, ngx-rust는 요청 핸들러에 더 많은 편의성과 유틸리티를 제공합니다.
아래 예제에서 볼 수 있듯이, ngx-rust는 요청 인스턴스와 함께 호출된 Rust 클로저를 수락하는 매크로 http_request_handler! 를 제공합니다. 또한 구성 및 변수를 가져오고, 해당 변수를 설정하고, 메모리, 기타 NGINX 프리미티브 및 API에 액세스하기 위한 유틸리티도 제공합니다.
핸들러 프로시저를 시작하려면 매크로를 호출하고 비즈니스 로직을 Rust 클로저로 제공하세요. ngx-rust-howto 모듈의 경우 요청이 계속 처리될 수 있도록 요청의 메서드를 확인합니다.
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("module config is none"); ngx_log_debug_http!(request, "howto module enabled called"); 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, } });
이것으로 첫 번째 Rust 모듈을 완성했습니다!
GitHub의 ngx-rust-howto 리포지토리에는 conf 디렉터리에 NGINX 구성 파일이 포함되어 있습니다. 또한 빌드(cargo build 사용)하고, 로컬 nginx.conf의 load_module 지시어에 모듈 바이너리를 추가하고, NGINX 인스턴스를 사용하여 실행할 수 있습니다. 이 튜토리얼을 작성할 때는 ngx-rust에서 지원하는 기본 NGINX_VERSION인 NGINX v1.23.3을 사용했습니다. 동적 모듈을 빌드하고 실행할 때는 컴퓨터에서 실행 중인 NGINX 인스턴스와 동일한 NGINX_VERSION를 ngx-rust 빌드에 사용해야 합니다.
결론
NGINX는 수년간의 기능과 사용 사례가 내장된 성숙한 소프트웨어 시스템입니다. 유능한 프록시, 로드 밸런서이자 세계적 수준의 웹 서버입니다. 앞으로 몇 년 동안 시장에서의 입지가 확실하기 때문에, 그 기능을 기반으로 사용자에게 새로운 상호 작용 방법을 제공하고자 하는 의욕이 생겼습니다. 개발자들 사이에서 Rust의 인기가 높아지고 안전성이 개선됨에 따라 세계 최고의 웹 서버와 함께 Rust를 사용할 수 있는 옵션을 제공하게 되어 매우 기쁘게 생각합니다.
하지만 NGINX의 성숙도와 풍부한 기능의 에코시스템으로 인해 API 표면적이 넓어졌고, ngx-rust는 이제 시작에 불과합니다. 이 프로젝트는 더 많은 관용적 Rust 인터페이스 추가, 추가 참조 모듈 구축, 모듈 작성의 인체공학적 개선을 통해 개선하고 확장하는 것을 목표로 합니다.
바로 여기에 여러분이 필요합니다! ngx-rust 프로젝트는 누구에게나 열려 있으며 GitHub에서 사용할 수 있습니다. 저희는 모듈의 기능과 사용 편의성을 지속적으로 개선하기 위해 NGINX 커뮤니티와 협력하고 있습니다. 직접 확인하고 바인딩을 실험해 보세요!
위 내용와 같이 NGINX Plus를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요
전문가에게 상담받기