Skip to content
Snippets Groups Projects
Unverified Commit cbee4a2f authored by Louis's avatar Louis :fire: Committed by GitHub
Browse files

Merge pull request #2 from Commander-lol/Scripting

### Added

- Lua scripting support as a route handler in the `config.yml` file
parents aab29e80 6d35988a
No related branches found
No related tags found
No related merge requests found
Showing
with 609 additions and 269 deletions
......@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
- [DEV] Initial test scaffolding
- Lua scripting support as a route handler in the `config.yml` file
## [0.2.0]
......@@ -34,4 +35,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Serve files from the target directory
- Serve index files from directories
- Configure number of worker threads
- Bind to custom address and port
\ No newline at end of file
- Bind to custom address and port
This diff is collapsed.
......@@ -35,8 +35,8 @@ docopt = "0.8"
formdata = "0.12.2"
hyper = "0.10"
rand = "0.3"
rhai = "0.7"
serde_yaml = "0.7.3"
rlua = "0.13.0"
[dev-dependencies]
interpolate_idents = "0.2.4"
local accountDB = {}
accountDB['fancy-account'] = {
name = "Fancy Account",
widgets = { barfoo = "The best widget in town", bipbop = "A really good widget"}
}
accountDB['less-fancy-account'] = {
name = "Not Quite As Fancy Account",
widgets = { cooliowidget = "A really good widget that should be respected" }
}
accountDB['unrelated-account'] = {
name = "John Mysterio's Vault of Wonders",
widgets = { yes = "You buy widget, yes?", widget = "Widget is good!", flubber = "Green, Ready to Rock" }
}
account = accountDB[params.account_id]
res = empty_response()
res:set_header('Content-Type', 'application/json')
print("[WIDGETS] Looking for:", params.account_id, params.widget_id)
if not (account == nil) then
print("[WIDGETS] Found", account.name)
local widget = account.widgets[params.widget_id]
if not (widget == nil) then
res:set_status(200)
res:set_body(json_encode({ widget = widget }))
else
res:set_status(404)
res:set_body(json_encode({ message = "Could not find widget" }))
end
else
res:set_status(404)
res:set_body(json_encode({ message = "Could not find account" }))
end
return res
......@@ -2,16 +2,19 @@ field_handling: Log
file_handling: File
server:
port: 9000
requests:
- /users/@user_id : scripts/get_user_by_id.dyon
- /users :
routes:
- route: /users/@user_id
script: get_user.lua
- route: /accounts/@account_id/widgets/@widget_id
script: accounts/handle_widgets.lua
- route: /users
response:
failure_rate: 5
response_headers:
headers:
x-rate-limit: 100
x-rate-remaining: 96
x-rate-reset: 10/12/1990
response_type: application/json
response_body:
body:
count: 2
data:
- id: 1
......
print("We're going to the races");
foo = 1 + 1;
r = empty_response();
r:set_status(200);
r:set_header("X-Powered-By", "Swerve");
r:set_header("Content-Type", "text/plain");
r:set_body("This is my only response");
--return r
print(params.user_id)
r = response(200, "application/json", json_encode({ user_id = params.user_id, path = params.script_path }));
--
--r:unset_body();
--r:set_status(204);
return r
-- return response(200, "application/json", '{ "foo": ' .. foo .. ' }')
fn handle(foo: f64) -> {
println(foo)
{ foo: foo, bar: 123 }
}
\ No newline at end of file
......@@ -8,6 +8,7 @@
<body>
<h1>It's Swervin' Time</h1>
<p>This page is part of the swerve example, and includes a stylesheet and stuff.</p>
<img src="/files/adorable-puppy.jpg">
<script async src="js/say_hello.js"></script>
</body>
</html>
\ No newline at end of file
......@@ -7,6 +7,7 @@ use std::default::Default;
use serde::{Deserialize, Deserializer, de};
use std::fmt;
use serde_yaml as yaml;
use cli;
#[derive(Debug, Copy, Clone)]
pub enum HandlerMethod {
......@@ -52,6 +53,8 @@ pub struct SwerveConfig {
pub file_handling: HandlerMethod,
#[serde(default)]
pub server: ServerOptions,
#[serde(default="get_empty_routes")]
pub routes: Vec<cli::RouteHandler>,
}
#[derive(Deserialize, Debug, Clone)]
......@@ -74,12 +77,15 @@ fn get_default_address() -> String { String::from("localhost") }
fn get_default_quiet_attr() -> bool { false }
fn get_default_index_attr() -> bool { false }
fn get_empty_routes() -> Vec<cli::RouteHandler> { vec![] }
impl Default for SwerveConfig {
fn default() -> Self {
SwerveConfig {
field_handling: HandlerMethod::Log,
file_handling: HandlerMethod::Log,
server: ServerOptions::default(),
routes: get_empty_routes(),
}
}
}
......
use std::collections::HashMap;
#[derive(Clone, Debug, Deserialize)]
pub struct RouteHandler {
pub route: String,
pub response: Option<ResponseHandler>,
pub script: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct ResponseHandler {
failure_rate: Option<u32>,
#[serde(default="get_default_headers")]
headers: HashMap<String, String>,
}
fn get_default_headers() -> HashMap<String, String> { HashMap::new() }
mod cli;
mod config_file;
mod config_routes;
pub mod gpl;
pub use self::cli::{Args, USAGE};
pub use self::config_file::{HandlerMethod, SwerveConfig};
\ No newline at end of file
pub use self::config_file::{HandlerMethod, SwerveConfig};
pub use self::config_routes::RouteHandler;
\ No newline at end of file
......@@ -4,12 +4,12 @@
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_yaml;
extern crate rhai;
extern crate rocket;
extern crate rocket_contrib;
extern crate formdata;
extern crate hyper;
extern crate rand;
extern crate rlua;
pub mod cli;
pub mod routing;
......
......@@ -6,7 +6,6 @@ extern crate alloc_system;
extern crate rocket;
extern crate docopt;
extern crate swerve;
extern crate rhai;
use std::process;
use docopt::Docopt;
......
use rocket::{Data, State};
use formdata::{read_formdata, FilePart};
use routing::request::ConvertedHeaders;
use hyper::header::{Headers, ContentDisposition, DispositionParam};
use rocket::request::FromRequest;
use std::io::{Read, Write, copy};
use std::io::{BufReader, BufWriter};
use hyper::header::{ContentDisposition, DispositionParam};
use std::io::{copy};
use std::fs::{OpenOptions, File, create_dir};
use cli::{HandlerMethod, SwerveConfig};
use std::path::{Path, PathBuf};
use std::path::{PathBuf};
use std::collections::HashMap;
use rand::{Rng, StdRng};
#[post(path = "/", data = "<upload>")]
pub fn to_file(headers: ConvertedHeaders, conf: State<SwerveConfig>, upload: Data) -> Result<String, String> {
......@@ -21,7 +18,12 @@ pub fn to_file(headers: ConvertedHeaders, conf: State<SwerveConfig>, upload: Dat
HandlerMethod::File => println!("{:?}", fields),
}
create_dir("uploads");
match create_dir("uploads") {
Ok(_) => {},
Err(err) => {
return Err(format!("Could not create uploads directory:\n{}", err));
}
}
for file in data.files {
match conf.file_handling {
......@@ -49,17 +51,17 @@ type Upload = (String, FilePart);
fn upload_file(file: Upload) {
let (name, value) = file;
let content_disposition = value.headers.get::<ContentDisposition>().unwrap();
let file_name = filename_from_disposition(content_disposition);
let file_name = filename_from_disposition(content_disposition).unwrap_or(name);
let mut input = File::open(value.path.clone()).unwrap();
let mut output = OpenOptions::new()
.write(true)
.create(true)
.open(PathBuf::from("uploads").join(file_name.clone().unwrap_or(String::from("upload_data"))))
.open(PathBuf::from("uploads").join(file_name.clone()))
.unwrap();
copy(&mut input, &mut output).unwrap();
println!("File written to {}", file_name.unwrap());
println!("File written to {}", file_name);
}
fn log_file(file: Upload) {
......
......@@ -2,3 +2,7 @@ pub mod mock_upload;
pub mod request;
pub mod scripting;
pub mod core;
mod request_rewriter;
pub use self::request_rewriter::{ScriptMap, RedirectScripts};
......@@ -57,3 +57,75 @@ impl rocket::response::Responder<'static> for TypedFile {
Ok(response)
}
}
pub struct RequestPath(pub String);
impl <'a, 'req>FromRequest<'a, 'req> for RequestPath {
type Error = ();
fn from_request(request: &'a Request<'req>) -> Outcome<Self, (http::Status, ()), ()> {
let uri = request.uri();
Outcome::Success(RequestPath(String::from(uri.path())))
}
}
pub mod path {
use std::path as std_path;
use std::option::Option::*;
#[derive(PartialEq, Eq, Hash)]
pub struct MatchablePath(pub String);
pub type PathMatch = (String, String);
pub type MatchList = Vec<PathMatch>;
pub type MatchResult = Option<MatchList>;
impl MatchablePath {
pub fn from<T>(src: T) -> Self where T: ToString {
MatchablePath(src.to_string())
}
fn path_to_vec<T: ToString>(string: T) -> Vec<String> {
std_path::PathBuf::from(string.to_string())
.components()
.filter_map(|c| match c {
std_path::Component::Normal(cmp) => cmp.to_str()
.and_then(|s| Some(String::from(s))),
_ => None,
})
.collect()
}
pub fn matches<T>(&self, other: T) -> MatchResult where T: ToString {
let this_path = MatchablePath::path_to_vec(&self.0);
let other_path = MatchablePath::path_to_vec(other);
if this_path.len() == other_path.len() {
let parts_match = this_path.iter()
.zip(other_path.iter())
.fold(true, |acc, (this, other)| {
acc && (this.starts_with("@") || this == other)
});
if parts_match {
Some(this_path.iter()
.zip(other_path.iter())
.filter_map(|(this, other)| {
if this.starts_with("@") {
Some((
this.chars().skip(1).collect::<String>(),
other.clone()
))
} else {
None
}
})
.collect()
)
} else {
None
}
} else {
None
}
}
}
}
use rocket::fairing::{Fairing, Info, Kind};
use rocket::{Request, Data, State};
use cli::SwerveConfig;
use std::collections::HashMap;
use routing::request::path::MatchablePath;
pub struct ScriptMap(pub HashMap<MatchablePath, String>);
impl ScriptMap {
pub fn from_config(conf: &SwerveConfig) -> Self {
let mut map: HashMap<MatchablePath, String> = HashMap::new();
for handler in &conf.routes {
if let Some(ref script) = handler.script {
map.insert(MatchablePath::from(handler.route.clone()), script.clone());
}
}
ScriptMap(map)
}
}
pub struct RedirectScripts;
impl Fairing for RedirectScripts {
fn info(&self) -> Info {
Info {
name: "Redirect Scripts To Handler",
kind: Kind::Request,
}
}
fn on_request(&self, request: &mut Request, _: &Data) {
let script_map_container = request.guard::<State<ScriptMap>>().unwrap();
let script_map: &HashMap<MatchablePath, String> = &script_map_container.0;
for path in script_map.keys() {
let matches = path.matches(request.uri().path());
if let Some(values) = matches {
let script_name = script_map.get(path).unwrap();
let mut value_buffer = String::new();
value_buffer.push_str("script_path=");
value_buffer.push_str(script_name);
for (ref param, ref val) in values {
value_buffer.push_str("&");
value_buffer.push_str(param);
value_buffer.push_str("=");
value_buffer.push_str(val);
}
let path = format!("/__run_script__/?{}", value_buffer);
request.set_uri(path);
break;
}
}
}
}
\ No newline at end of file
use scripting::run_script;
use std::path::PathBuf;
#[get("/__testing__/run-script")]
pub fn route_script() -> String {
let path = PathBuf::from("example/.swerve/get_user_by_id.rhai");
run_script(path).unwrap_or(String::from("No script"))
}
\ No newline at end of file
use server::LuaRuntime;
use rlua::{Lua};
use scripting::{run_script, ScriptResponse};
use rocket::request::{FromForm, FormItems};
use std::collections::HashMap;
#[derive(Debug)]
pub struct ScriptParams {
pub script_name: String,
pub script_params: HashMap<String, String>,
}
impl <'form> FromForm<'form> for ScriptParams {
type Error = ();
fn from_form(items: &mut FormItems<'form>, _: bool) -> Result<ScriptParams, Self::Error> {
let mut script_name: Option<String> = None;
let mut script_params: HashMap<String, String> = HashMap::new();
for (key, value) in items {
match key.as_str() {
"script_path" if script_name.is_none() => { script_name = Some(String::from(value.as_str())); },
_ => { script_params.insert(String::from(key.as_str()), String::from(value.as_str())); },
};
}
match script_name {
Some(name) => Ok(ScriptParams { script_name: name, script_params }),
None => Err(()),
}
}
}
#[get("/__run_script__?<params>")]
pub fn route_script(params: ScriptParams, runtime: LuaRuntime) -> ScriptResponse {
let lua: Lua = runtime.into();
run_script(format!("example/.swerve/{}", params.script_name), &lua, params.script_params).unwrap_or_else(|| ScriptResponse::default())
}
pub mod script_request;
mod run_script;
mod script_response;
pub use self::run_script::run_script;
\ No newline at end of file
pub use self::run_script::run_script;
pub use self::script_response::ScriptResponse;
\ No newline at end of file
//use dyon::{Runtime,Module,load_str,Variable,Dfn,Type};
//use dyon::ast::convert;
use std::convert::AsRef;
use std::path::Path;
use std::fs::File;
use std::sync::Arc;
use std::io::Read;
use rlua::Lua;
use scripting::ScriptResponse;
use std::collections::HashMap;
const SCRIPT_FOOTER: &'static str = "
fn main() {
println(\"Don't directly execute the module, nimrod\")
}";
pub fn run_script<P: AsRef<Path>>(path: P, lua: &Lua, params: HashMap<String, String>) -> Option<ScriptResponse> {
let mut file = File::open(&path).unwrap();
let mut buf = String::new();
match file.read_to_string(&mut buf) {
Ok(_) => {},
Err(_) => return None,
}
pub fn run_script<P: AsRef<Path>>(path: P) -> Option<String> {
// let mut resolution: Option<Variable> = None;
// dyon_fn!(fn resolve(val: Variable) {
// resolution = Some(val); // if let Ok(val) = runtime.pop() { Some(val) } else { None };
// });
//
// println!("{:?}", resolution);
let file_name = path.as_ref()
.file_name()
.and_then(|name| name.to_str());
let mut file = File::open(&path).unwrap();
let mut buf = String::new();
let params_table = serialize_table("params", params);
lua.eval::<ScriptResponse>(&format!("{}\n{}", params_table, buf), file_name)
.map_err(|e| println!("{}", e))
.ok()
}
fn serialize_table(name: &'static str, table: HashMap<String, String>) -> String {
let mut output = format!("local {} = {{", name);
let contents = table.iter()
.fold(
String::new(),
|acc, (key, value)| format!("{} {} = \"{}\",", acc, key, value)
);
output.push_str(&contents[0..contents.len() - 1]);
output.push_str(" }");
file.read_to_string(&mut buf);
buf.push_str(SCRIPT_FOOTER);
// let mut script_module = Module::new();
//
// {
// load_str(path.as_ref().to_str().unwrap(), Arc::new(buf), &mut script_module);
// }
Some(buf)
// script_module.add(Arc::new("resolve".into()), resolve, Dfn {
// lts: vec![],
// tys: vec![Type::Object],
// ret: Type::Void,
// });
//
// let mut hashmap: HashMap<Arc<String>, Variable> = HashMap::new();
// runtime.call_str("handle", &[Variable::f64(123f64)], &Arc::new(script_module));
// None
}
\ No newline at end of file
output
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment