extern crate cpython;
use std::env;
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::Write;
use std::error::Error;
use std::fmt;
use std::convert::From;
use self::cpython::{Python, PythonObject, PyString, PyList, PyDict, PythonObjectWithCheckedDowncast, PyResult, PyErr};
use utils::file::locate_file;
use request::Request;
use server::{ServerApplication, InternalServerError};
type PythonResult<T> = Result<T, PythonError>;
#[derive(Debug)]
pub struct PythonError(PyErr);
impl Error for PythonError {
fn description(&self) -> &str {
"Error calling python application."
}
}
impl fmt::Display for PythonError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?} ({:?})", self.0.ptype, self.0.pvalue)
}
}
impl From<PythonError> for InternalServerError {
fn from(p: PythonError) -> Self {
Self{0: p.description().to_string(), 1: None}
}
}
fn convert_error<T>(p: PyResult<T>) -> PythonResult<T> {
match p {
Ok(v) => Ok(v),
Err(e) => Err(PythonError(e))
}
}
#[derive(Debug, Clone)]
pub struct Application {
module: String,
callable: String,
headers_set: Vec<String>,
path_to_app: PathBuf,
path_to_bindings: String,
port: String,
}
impl Application {
fn parse_app_string(string: &str) -> (String, String) {
let split: Vec<&str> = string.split(':').collect();
(split[0].to_string(), split[1].to_string())
}
fn call_application(&self, request: Request, py: Python) -> PyResult<String> {
let locals = PyDict::new(py);
let env = self.set_env(request, py)?;
let sys = py.import("sys")?;
let sys_path = sys.get(py, "path")?;
let path_as_py_object = PyString::new(py, self.path_to_app.to_str().unwrap_or("")).into_object();
let bindings_path_as_py_object = PyString::new(py, &self.path_to_bindings).into_object();
let path_list = PyList::downcast_from(py, sys_path)?;
path_list.insert_item(py, 0, path_as_py_object);
path_list.insert_item(py, 1, bindings_path_as_py_object);
let imported_module = py.import(&self.module)?;
locals.set_item(py, &self.module.clone(), imported_module)?;
locals.set_item(py, "bind", py.import("bindings")?)?;
locals.set_item(py, "env", env)?;
let call_statement = format!("bind.Application.call_callable(env, {}.{})", self.module, self.callable);
let result = py.eval(&call_statement, None, Some(&locals))?;
let value = result.extract(py)?;
Ok(value)
}
fn create_bindings() -> String {
let mut path = env::current_exe().unwrap();
path.set_file_name("bindings.py");
if Path::exists(&path) {
String::from(path .to_str().unwrap())
} else {
static BINDINGS_TEXT: &[u8] = include_bytes!("bindings.py");
let mut file = File::create(&path).unwrap();
file.write_all(BINDINGS_TEXT).expect("Could not write WSGI bindings");
String::from(path.to_str().unwrap())
}
}
fn set_env(&self, request: Request, py: Python) -> PyResult<PyDict> {
let env = PyDict::new(py);
env.set_item(py, "wsgi.version", "1.0")?;
env.set_item(py, "wsgi.url_scheme", "http")?;
env.set_item(py, "wsgi.input", request.data)?;
env.set_item(py, "wsgi.errors", "2>")?;
env.set_item(py, "wsgi.multithread", true)?;
env.set_item(py, "wsgi.multiprocess", true)?;
env.set_item(py, "wsgi.run_once", false)?;
env.set_item(py, "REQUEST_METHOD", format!("{}", request.kind))?;
env.set_item(py, "PATH_INFO", request.path)?;
env.set_item(py, "SERVER_NAME", format!("{}", request.host))?;
env.set_item(py, "SERVER_PORT", format!("{}", self.port))?;
Ok(env)
}
}
impl ServerApplication for Application {
fn create(app_string: Option<&String>, port: &str) -> Option<Application> {
let app_string = app_string?;
let port = port.to_string();
let (module, callable) = Application::parse_app_string(app_string);
let headers_set = Vec::new();
let path_to_app = match locate_file(&module) {
Some(location) => location,
None => return None,
};
let created_at = Application::create_bindings();
let mut created_at = PathBuf::from(created_at);
created_at.set_file_name("");
let path_to_bindings = String::from(created_at.to_str().unwrap());
let app = Application {
module,
callable,
headers_set,
path_to_app,
path_to_bindings,
port,
};
Some(app)
}
fn handle_one_request(&self, request: Request)-> Result<String, InternalServerError> {
let gil = Python::acquire_gil();
let result = convert_error(self.call_application(request, gil.python()))?;
Ok(result)
}
}
#[cfg(test)]
mod tests {
extern crate reqwest;
use std::env;
use std::thread;
use config::Config;
use server::Server;
#[test]
fn test_flask_integration() {
let mut current = env::current_dir().unwrap();
current.push("src/wsgi/test_scripts");
let test_port = "9595";
let config = create_test_config(&test_port, ¤t.to_str().unwrap());
let server = Server::from_config(config);
thread::spawn(move || { server.serve() } );
let mut request = reqwest::get(&format!("http://127.0.0.1:{}", test_port)).unwrap();
assert!(request.status().is_success());
assert_eq!(request.text().unwrap(), "Hello, World!");
}
fn create_test_config(port: &str, location: &str) -> Config {
let text = r#"{
"host": "127.0.0.1",
"port": "{port}",
"app": "flask_test_app:app",
"app_path": "{location}"
}"#.to_string();
let result = text.replace("{port}", port);
let result = result.replace("{location}", location);
Config::from_json(&result)
}
}