Skip to content

Commit

Permalink
feat(wasm): move webassembly directly to main library
Browse files Browse the repository at this point in the history
  • Loading branch information
joelwurtz committed May 14, 2024
1 parent d7bd51f commit 2882c91
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 16 deletions.
15 changes: 11 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "redirectionio"
description = "Redirection IO Library to handle matching rule, redirect and filtering headers and body."
repository = "https://github.com/redirectionio/libredirectionio"
license = "MIT"
version = "2.11.0"
version = "2.11.1"
authors = ["Joel Wurtz <[email protected]>"]
edition = "2018"
build = "src/build.rs"
Expand All @@ -27,6 +27,16 @@ compress = ["dep:brotli", "dep:flate2"]
router = []
dot = ["dep:dot_graph"]

[target.'cfg(target_arch = "wasm32")'.dependencies]
chrono = { version = "0.4.19", features = ["serde", "wasmbind"] }
futures-util = { version = "0.3.21", default-features = false }
getrandom = { version = "0.2.8", features = ["js"] }
log = "0.4.21"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.70"
wasm-bindgen = "0.2.84"
wasm-logger = "0.2.0"

[dependencies]
brotli = { version = "3.5.0", optional = true }
cfg-if = "1.0.0"
Expand All @@ -53,9 +63,6 @@ url = "2.5.0"
pprof = { version = "0.13", features = ["flamegraph"] }
criterion = { version = "0.5.1", default-features = false }

[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = { version = "0.1.7" }

[[bench]]
name = "match_rule_benchmark"
harness = false
Expand Down
4 changes: 2 additions & 2 deletions src/action/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub unsafe extern "C" fn redirectionio_action_json_deserialize(str: *mut c_char)

let action = match json_decode(action_str) {
Err(error) => {
error!("Unable to deserialize \"{}\" to action: {}", action_str, error,);
log::error!("Unable to deserialize \"{}\" to action: {}", action_str, error,);

return null();
}
Expand All @@ -40,7 +40,7 @@ pub unsafe extern "C" fn redirectionio_action_json_serialize(_action: *mut Actio
let action = &*_action;
let action_serialized = match json_encode(action) {
Err(error) => {
error!("Unable to serialize to action: {}", error,);
log::error!("Unable to serialize to action: {}", error,);

return null();
}
Expand Down
2 changes: 1 addition & 1 deletion src/api/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Rule {
let rule_result = json_decode(rule_str);

if rule_result.is_err() {
error!("Unable to create rule from string {}: {}", rule_str, rule_result.err().unwrap());
log::error!("Unable to create rule from string {}: {}", rule_str, rule_result.err().unwrap());

return None;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ffi_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub unsafe fn c_char_to_str(ptr: *const c_char) -> Option<&'static str> {

match CStr::from_ptr(ptr).to_str() {
Err(error) => {
error!(
log::error!(
"Unable to create string for '{}': {}",
String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()),
error,
Expand All @@ -24,7 +24,7 @@ pub unsafe fn c_char_to_str(ptr: *const c_char) -> Option<&'static str> {
pub unsafe fn string_to_c_char(str: String) -> *const c_char {
let string = match CString::new(str.as_str()) {
Err(error) => {
error!("Cannot create c string {}: {}", str, error,);
log::error!("Cannot create c string {}: {}", str, error,);

return null();
}
Expand Down
1 change: 1 addition & 0 deletions src/filter/html_filter_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::action::UnitTrace;
use crate::filter::error::Result;
use crate::filter::html_body_action::HtmlBodyVisitor;
use crate::html;
use lazy_static::lazy_static;
use std::collections::HashSet;

#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions src/http/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub unsafe extern "C" fn redirectionio_request_json_deserialize(str: *mut c_char

let request = match json_decode(request_str) {
Err(err) => {
error!("cannot deserialize request {} for string {}", err, request_str);
log::error!("cannot deserialize request {} for string {}", err, request_str);

return null();
}
Expand Down Expand Up @@ -199,7 +199,7 @@ pub unsafe extern "C" fn redirectionio_request_from_str(_url: *const c_char) ->

match url.parse::<Request>() {
Err(err) => {
error!("cannot create request for url {}: {}", url, err);
log::error!("cannot create request for url {}: {}", url, err);

null()
}
Expand Down
7 changes: 2 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ This crate provides a library for matching, handling and logging http requests w
rule format.
*/

#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;

pub mod action;
pub mod api;
pub mod filter;
Expand All @@ -27,5 +22,7 @@ mod dot;
mod ffi_helpers;
mod regex;
mod router_config;
#[cfg(target_arch = "wasm32")]
mod wasm_api;

pub use router_config::RouterConfig;
2 changes: 2 additions & 0 deletions src/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct LazyRegex {
}

impl LazyRegex {
#[cfg(feature = "router")]
pub fn new_node(regex: String, ignore_case: bool) -> LazyRegex {
LazyRegex {
regex: if regex.is_empty() {
Expand All @@ -32,6 +33,7 @@ impl LazyRegex {
}
}

#[cfg(feature = "router")]
pub fn is_match(&self, value: &str) -> bool {
match &self.compiled {
Some(regex) => regex.is_match(value),
Expand Down
231 changes: 231 additions & 0 deletions src/wasm_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
use crate::action::Action as RedirectionioAction;
use crate::api::Log;
use crate::filter::FilterBodyAction;
use crate::http::{Header, PathAndQueryWithSkipped, Request as RedirectionioRequest, TrustedProxies};
use crate::RouterConfig;
use chrono::Utc;
use serde_json::{from_str as json_decode, to_string as json_encode};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use wasm_bindgen::prelude::*;

#[wasm_bindgen()]
pub struct Request {
#[wasm_bindgen(skip)]
pub request: RedirectionioRequest,
}

#[wasm_bindgen()]
pub struct HeaderMap {
#[wasm_bindgen(skip)]
pub headers: Vec<Header>,
}

#[wasm_bindgen()]
pub struct Action {
#[wasm_bindgen(skip)]
pub action: Option<RedirectionioAction>,
}

#[wasm_bindgen()]
pub struct BodyFilter {
#[wasm_bindgen(skip)]
pub filter: Option<FilterBodyAction>,
}

#[wasm_bindgen()]
impl Request {
#[wasm_bindgen(constructor)]
pub fn new(uri: String, host: String, scheme: String, method: String) -> Request {
let config = RouterConfig::default();

Request {
request: RedirectionioRequest {
headers: Vec::new(),
host: Some(host),
method: Some(method),
scheme: Some(scheme),
path_and_query_skipped: PathAndQueryWithSkipped::from_config(&config, uri.as_str()),
path_and_query: Some(uri),
remote_addr: None,
created_at: Some(Utc::now()),
sampling_override: None,
},
}
}

pub fn set_remote_ip(&mut self, remote_addr_str: String) {
self.request.set_remote_ip(remote_addr_str, &TrustedProxies::default());
}

pub fn add_header(&mut self, name: String, value: String) {
self.request.add_header(name, value, false)
}

pub fn serialize(&self) -> String {
match json_encode(&self.request) {
Err(_) => "".to_string(),
Ok(request_serialized) => request_serialized,
}
}

pub fn get_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.request.hash(&mut hasher);

hasher.finish()
}
}

#[wasm_bindgen()]
impl HeaderMap {
#[allow(clippy::new_without_default)]
#[wasm_bindgen(constructor)]
pub fn new() -> HeaderMap {
HeaderMap { headers: Vec::new() }
}

pub fn add_header(&mut self, name: String, value: String) {
self.headers.push(Header { name, value })
}

pub fn remove_header(&mut self, name: String) {
self.headers.retain(|header| header.name != name)
}

pub fn len(&self) -> usize {
self.headers.len()
}

pub fn is_empty(&self) -> bool {
self.headers.is_empty()
}

pub fn get_header_name(&self, index: usize) -> String {
match self.headers.get(index) {
None => "".to_string(),
Some(header) => header.name.clone(),
}
}

pub fn get_header_value(&self, index: usize) -> String {
match self.headers.get(index) {
None => "".to_string(),
Some(header) => header.value.clone(),
}
}
}

#[wasm_bindgen()]
impl Action {
#[wasm_bindgen(constructor)]
pub fn new(action_serialized: String) -> Action {
let action = match json_decode(action_serialized.as_str()) {
Err(error) => {
log::error!("Unable to deserialize \"{}\" to action: {}", action_serialized, error,);

None
}
Ok(action) => Some(action),
};

Action { action }
}

pub fn empty() -> Action {
Action { action: None }
}

pub fn get_status_code(&mut self, response_status_code: u16) -> u16 {
if let Some(action) = self.action.as_mut() {
return action.get_status_code(response_status_code, None);
}

0
}

pub fn filter_headers(&mut self, headers: HeaderMap, response_status_code: u16, add_rule_ids_header: bool) -> HeaderMap {
if self.action.is_none() {
return headers;
}

let action = self.action.as_mut().unwrap();
let new_headers = action.filter_headers(headers.headers, response_status_code, add_rule_ids_header, None);

HeaderMap { headers: new_headers }
}

pub fn create_body_filter(&mut self, response_status_code: u16, headers: &HeaderMap) -> BodyFilter {
if self.action.is_none() {
return BodyFilter { filter: None };
}

let action = self.action.as_mut().unwrap();
let filter = action.create_filter_body(response_status_code, &headers.headers);

BodyFilter { filter }
}

pub fn should_log_request(&mut self, response_status_code: u16) -> bool {
if self.action.is_none() {
return true;
}

let action = self.action.as_mut().unwrap();

action.should_log_request(true, response_status_code, None)
}
}

#[wasm_bindgen()]
impl BodyFilter {
pub fn is_null(&self) -> bool {
self.filter.is_none()
}

pub fn filter(&mut self, data: Vec<u8>) -> Vec<u8> {
match self.filter.as_mut() {
None => data,
Some(filter) => filter.filter(data, None),
}
}

pub fn end(&mut self) -> Vec<u8> {
match self.filter.as_mut() {
None => Vec::new(),
Some(filter) => filter.end(None),
}
}
}

#[wasm_bindgen()]
pub fn init_log() {
wasm_logger::init(wasm_logger::Config::default());
}

#[wasm_bindgen()]
pub fn create_log_in_json(
request: Request,
status_code: u16,
response_headers: HeaderMap,
action: &Action,
proxy: String,
time: u64,
client_ip: String,
) -> String {
let log = Log::from_proxy(
&request.request,
status_code,
&response_headers.headers,
action.action.as_ref(),
proxy.as_str(),
time.into(),
client_ip.as_str(),
None,
);

match json_encode(&log) {
Err(_) => "".to_string(),
Ok(s) => s,
}
}

0 comments on commit 2882c91

Please sign in to comment.