chore: make more generic
This commit is contained in:
1
.envrc
1
.envrc
@ -2,3 +2,4 @@ use flake
|
|||||||
unset TMPDIR
|
unset TMPDIR
|
||||||
export NGINX_SOURCE_DIR=$PWD/ngx_src/nginx-1.28.0
|
export NGINX_SOURCE_DIR=$PWD/ngx_src/nginx-1.28.0
|
||||||
export ZLIB_VERSION=1.3.1
|
export ZLIB_VERSION=1.3.1
|
||||||
|
export CFLAGS='-Wno-unterminated-string-initialization'
|
||||||
|
|||||||
@ -14,7 +14,9 @@ http {
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
add_header "Content-Type" "text/html";
|
add_header "Content-Type" "text/html";
|
||||||
howto "GET";
|
sqlite_db "db.sqlite3";
|
||||||
|
sqlite_query "SELECT id, name, address FROM person";
|
||||||
|
sqlite_template "person.hbs";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{{#each persons}}
|
{{#each results}}
|
||||||
<li>Person: {{ id }}. {{ name }}
|
<li>Person: {{ id }}. {{ name }}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
273
src/lib.rs
273
src/lib.rs
@ -13,8 +13,6 @@ use ngx::{http_request_handler, ngx_log_debug_http, ngx_modules, ngx_null_comman
|
|||||||
use rusqlite::{Connection, Result};
|
use rusqlite::{Connection, Result};
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::os::raw::{c_char, c_void};
|
use std::os::raw::{c_char, c_void};
|
||||||
use std::ptr::addr_of;
|
use std::ptr::addr_of;
|
||||||
|
|
||||||
@ -28,46 +26,46 @@ impl http::HTTPModule for Module {
|
|||||||
type LocConf = ModuleConfig;
|
type LocConf = ModuleConfig;
|
||||||
|
|
||||||
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
|
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
|
||||||
let htcf = http::ngx_http_conf_get_module_main_conf(cf, &*addr_of!(ngx_http_core_module));
|
unsafe {
|
||||||
|
let htcf = http::ngx_http_conf_get_module_main_conf(cf, &*addr_of!(ngx_http_core_module));
|
||||||
|
|
||||||
let h = ngx_array_push(
|
let h = ngx_array_push(
|
||||||
&mut (*htcf).phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers,
|
&mut (*htcf).phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers,
|
||||||
) as *mut ngx_http_handler_pt;
|
) as *mut ngx_http_handler_pt;
|
||||||
if h.is_null() {
|
if h.is_null() {
|
||||||
return core::Status::NGX_ERROR.into();
|
return core::Status::NGX_ERROR.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set an Access phase handler
|
||||||
|
*h = Some(howto_access_handler);
|
||||||
|
core::Status::NGX_OK.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// set an Access phase handler
|
|
||||||
*h = Some(howto_access_handler);
|
|
||||||
core::Status::NGX_OK.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a ModuleConfig to save our configuration state.
|
// Create a ModuleConfig to save our configuration state.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct ModuleConfig {
|
struct ModuleConfig {
|
||||||
enabled: bool,
|
db_path: String,
|
||||||
method: String,
|
query: String,
|
||||||
|
template_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement our Merge trait to merge configuration with higher layers.
|
// Implement our Merge trait to merge configuration with higher layers.
|
||||||
impl http::Merge for ModuleConfig {
|
impl http::Merge for ModuleConfig {
|
||||||
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
|
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
|
||||||
if prev.enabled {
|
if self.db_path.is_empty() {
|
||||||
self.enabled = true;
|
self.db_path = prev.db_path.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.method.is_empty() {
|
if self.query.is_empty() {
|
||||||
self.method = String::from(if !prev.method.is_empty() {
|
self.query = prev.query.clone();
|
||||||
&prev.method
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.enabled && self.method.is_empty() {
|
if self.template_path.is_empty() {
|
||||||
return Err(MergeConfigError::NoValue);
|
self.template_path = prev.template_path.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,11 +124,27 @@ pub static mut ngx_http_howto_module: ngx_module_t = ngx_module_t {
|
|||||||
// Register and allocate our command structures for directive generation and eventual storage. Be
|
// Register and allocate our command structures for directive generation and eventual storage. Be
|
||||||
// sure to terminate the array with the ngx_null_command! macro.
|
// sure to terminate the array with the ngx_null_command! macro.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
static mut ngx_http_howto_commands: [ngx_command_t; 2] = [
|
static mut ngx_http_howto_commands: [ngx_command_t; 4] = [
|
||||||
ngx_command_t {
|
ngx_command_t {
|
||||||
name: ngx_string!("howto"),
|
name: ngx_string!("sqlite_db"),
|
||||||
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
||||||
set: Some(ngx_http_howto_commands_set_method),
|
set: Some(ngx_http_howto_commands_set_db_path),
|
||||||
|
conf: NGX_RS_HTTP_LOC_CONF_OFFSET,
|
||||||
|
offset: 0,
|
||||||
|
post: std::ptr::null_mut(),
|
||||||
|
},
|
||||||
|
ngx_command_t {
|
||||||
|
name: ngx_string!("sqlite_query"),
|
||||||
|
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
||||||
|
set: Some(ngx_http_howto_commands_set_query),
|
||||||
|
conf: NGX_RS_HTTP_LOC_CONF_OFFSET,
|
||||||
|
offset: 0,
|
||||||
|
post: std::ptr::null_mut(),
|
||||||
|
},
|
||||||
|
ngx_command_t {
|
||||||
|
name: ngx_string!("sqlite_template"),
|
||||||
|
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
||||||
|
set: Some(ngx_http_howto_commands_set_template_path),
|
||||||
conf: NGX_RS_HTTP_LOC_CONF_OFFSET,
|
conf: NGX_RS_HTTP_LOC_CONF_OFFSET,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
post: std::ptr::null_mut(),
|
post: std::ptr::null_mut(),
|
||||||
@ -139,7 +153,7 @@ static mut ngx_http_howto_commands: [ngx_command_t; 2] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn ngx_http_howto_commands_set_method(
|
extern "C" fn ngx_http_howto_commands_set_db_path(
|
||||||
cf: *mut ngx_conf_t,
|
cf: *mut ngx_conf_t,
|
||||||
_cmd: *mut ngx_command_t,
|
_cmd: *mut ngx_command_t,
|
||||||
conf: *mut c_void,
|
conf: *mut c_void,
|
||||||
@ -147,39 +161,80 @@ extern "C" fn ngx_http_howto_commands_set_method(
|
|||||||
unsafe {
|
unsafe {
|
||||||
let conf = &mut *(conf as *mut ModuleConfig);
|
let conf = &mut *(conf as *mut ModuleConfig);
|
||||||
let args = (*(*cf).args).elts as *mut ngx_str_t;
|
let args = (*(*cf).args).elts as *mut ngx_str_t;
|
||||||
conf.enabled = true;
|
conf.db_path = (*args.add(1)).to_string();
|
||||||
conf.method = (*args.add(1)).to_string();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ptr::null_mut()
|
std::ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[unsafe(no_mangle)]
|
||||||
struct Person {
|
extern "C" fn ngx_http_howto_commands_set_query(
|
||||||
id: u64,
|
cf: *mut ngx_conf_t,
|
||||||
name: String,
|
_cmd: *mut ngx_command_t,
|
||||||
address: String,
|
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.query = (*args.add(1)).to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Person {
|
#[unsafe(no_mangle)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
extern "C" fn ngx_http_howto_commands_set_template_path(
|
||||||
write!(f, "Person ({}, {}, {})", self.id, self.name, self.address)
|
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.template_path = (*args.add(1)).to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_person() -> Result<Vec<Person>> {
|
// Execute a generic SQL query and return results as JSON-compatible data
|
||||||
let conn = Connection::open("db.sqlite3")?;
|
fn execute_query(db_path: &str, query: &str) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
|
||||||
let mut stmt = conn.prepare("SELECT id, name, address FROM person")?;
|
let conn = Connection::open(db_path)?;
|
||||||
|
let mut stmt = conn.prepare(query)?;
|
||||||
|
|
||||||
|
let column_count = stmt.column_count();
|
||||||
|
let column_names: Vec<String> = (0..column_count)
|
||||||
|
.map(|i| stmt.column_name(i).unwrap_or("").to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let person_iter = stmt.query_map([], |row| {
|
let rows = stmt.query_map([], |row| {
|
||||||
Ok(Person {
|
let mut map = std::collections::HashMap::new();
|
||||||
id: row.get(0)?,
|
for (i, col_name) in column_names.iter().enumerate() {
|
||||||
name: row.get(1)?,
|
let value: serde_json::Value = match row.get_ref(i)? {
|
||||||
address: row.get(2)?,
|
rusqlite::types::ValueRef::Null => serde_json::Value::Null,
|
||||||
})
|
rusqlite::types::ValueRef::Integer(v) => serde_json::Value::Number(v.into()),
|
||||||
|
rusqlite::types::ValueRef::Real(v) => {
|
||||||
|
serde_json::Number::from_f64(v)
|
||||||
|
.map(serde_json::Value::Number)
|
||||||
|
.unwrap_or(serde_json::Value::Null)
|
||||||
|
}
|
||||||
|
rusqlite::types::ValueRef::Text(v) => {
|
||||||
|
serde_json::Value::String(String::from_utf8_lossy(v).to_string())
|
||||||
|
}
|
||||||
|
rusqlite::types::ValueRef::Blob(v) => {
|
||||||
|
// Convert blob to hex string
|
||||||
|
let hex_string = v.iter()
|
||||||
|
.map(|b| format!("{:02x}", b))
|
||||||
|
.collect::<String>();
|
||||||
|
serde_json::Value::String(hex_string)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
map.insert(col_name.clone(), value);
|
||||||
|
}
|
||||||
|
Ok(map)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
person_iter.collect()
|
rows.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement a request handler. Use the convenience macro, the http_request_handler! macro will
|
// Implement a request handler. Use the convenience macro, the http_request_handler! macro will
|
||||||
@ -188,73 +243,69 @@ fn get_person() -> Result<Vec<Person>> {
|
|||||||
//
|
//
|
||||||
// The function body is implemented as a Rust closure.
|
// The function body is implemented as a Rust closure.
|
||||||
http_request_handler!(howto_access_handler, |request: &mut http::Request| {
|
http_request_handler!(howto_access_handler, |request: &mut http::Request| {
|
||||||
let m_persons = get_person();
|
let co = unsafe {
|
||||||
|
request.get_module_loc_conf::<ModuleConfig>(&*addr_of!(ngx_http_howto_module))
|
||||||
|
};
|
||||||
|
let co = co.expect("module config is none");
|
||||||
|
|
||||||
let enabled = {
|
// Check if all required config values are set
|
||||||
let co = unsafe {
|
if co.db_path.is_empty() || co.query.is_empty() || co.template_path.is_empty() {
|
||||||
request.get_module_loc_conf::<ModuleConfig>(&*addr_of!(ngx_http_howto_module))
|
return core::Status::NGX_OK;
|
||||||
};
|
}
|
||||||
let co = co.expect("module config is none");
|
|
||||||
co.enabled
|
ngx_log_debug_http!(request, "sqlite module handler called");
|
||||||
|
|
||||||
|
// Execute the configured SQL query
|
||||||
|
let results = match execute_query(&co.db_path, &co.query) {
|
||||||
|
Ok(results) => results,
|
||||||
|
Err(e) => {
|
||||||
|
ngx_log_debug_http!(request, "failed to execute query: {}", e);
|
||||||
|
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ngx_log_debug_http!(request, "howto module enabled called");
|
// Setup Handlebars and register the configured template
|
||||||
let mut reg = Handlebars::new();
|
let mut reg = Handlebars::new();
|
||||||
match reg.register_template_file("person", "./person.hbs") {
|
match reg.register_template_file("template", &co.template_path) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_) => return http::HTTPStatus::INTERNAL_SERVER_ERROR.into(),
|
Err(e) => {
|
||||||
}
|
ngx_log_debug_http!(request, "failed to load template: {}", e);
|
||||||
|
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
||||||
match enabled {
|
|
||||||
true => {
|
|
||||||
match m_persons {
|
|
||||||
Ok(persons) => {
|
|
||||||
let body = match reg.render("person", &json!({"persons": persons})) {
|
|
||||||
Ok(body) => body,
|
|
||||||
Err(_) => return http::HTTPStatus::INTERNAL_SERVER_ERROR.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf = match request.pool().create_buffer_from_str(&body) {
|
|
||||||
Some(buf) => buf,
|
|
||||||
None => return http::HTTPStatus::INTERNAL_SERVER_ERROR.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
buf.set_last_buf(request.is_main());
|
|
||||||
buf.set_last_in_chain(true);
|
|
||||||
|
|
||||||
let mut out = ngx_chain_t {
|
|
||||||
buf: buf.as_ngx_buf_mut(),
|
|
||||||
next: std::ptr::null_mut(),
|
|
||||||
};
|
|
||||||
|
|
||||||
request.discard_request_body();
|
|
||||||
request.set_status(http::HTTPStatus::OK);
|
|
||||||
// request.set_content_length_n(full_len);
|
|
||||||
let rc = request.send_header();
|
|
||||||
if rc == core::Status::NGX_ERROR
|
|
||||||
|| rc > core::Status::NGX_OK
|
|
||||||
|| request.header_only()
|
|
||||||
{
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.output_filter(&mut out);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
//todo!();
|
|
||||||
ngx_log_debug_http!(request, "failed to find persons: {}", e);
|
|
||||||
|
|
||||||
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// let method = request.method();
|
|
||||||
|
|
||||||
// if method.as_str() == co.method {
|
|
||||||
// return core::Status::NGX_OK;
|
|
||||||
// }
|
|
||||||
Status::NGX_DONE
|
|
||||||
}
|
}
|
||||||
false => core::Status::NGX_OK,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render the template with query results
|
||||||
|
let body = match reg.render("template", &json!({"results": results})) {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(e) => {
|
||||||
|
ngx_log_debug_http!(request, "failed to render template: {}", e);
|
||||||
|
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create output buffer
|
||||||
|
let mut buf = match request.pool().create_buffer_from_str(&body) {
|
||||||
|
Some(buf) => buf,
|
||||||
|
None => return http::HTTPStatus::INTERNAL_SERVER_ERROR.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
buf.set_last_buf(request.is_main());
|
||||||
|
buf.set_last_in_chain(true);
|
||||||
|
|
||||||
|
let mut out = ngx_chain_t {
|
||||||
|
buf: buf.as_ngx_buf_mut(),
|
||||||
|
next: std::ptr::null_mut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
request.discard_request_body();
|
||||||
|
request.set_status(http::HTTPStatus::OK);
|
||||||
|
let rc = request.send_header();
|
||||||
|
if rc == core::Status::NGX_ERROR
|
||||||
|
|| rc > core::Status::NGX_OK
|
||||||
|
|| request.header_only()
|
||||||
|
{
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.output_filter(&mut out)
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user