Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F10778506
D712.id1835.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D712.id1835.diff
View Options
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,14 @@
amqp = "0.0.19"
env_logger = "^0.3"
iron = "0.4.*"
+lazy_static = "*"
log = "^0.3.5"
+num_cpus = "0.2"
+persistent= "^0.2.1"
+r2d2 = "^0.7"
+r2d2_sqlite = "^0.1.0"
router = "*"
-rust-sqlite = "0.3.0"
+rusqlite = "^0.7.3"
[dev-dependencies]
iron-test = "0.4"
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -51,3 +51,11 @@
Note this is an executable, not a library.
As such, if you want to add a test, you can't use a tests/ folder:
the independent test crate won't be able to import the crate.
+
+## Acknowledgment
+
+Thanks to the following individuals:
+
+ * Vincent Prouillet aka Keats
+ * Matthieu Wipliez
+
diff --git a/src/http_server.rs b/src/http_server.rs
--- a/src/http_server.rs
+++ b/src/http_server.rs
@@ -6,7 +6,11 @@
License: BSD-2-Clause
------------------------------------------------------------- */
+use store::SqliteDB;
+use store::SqlitePool;
+
use iron::prelude::*;
+use persistent::Read;
use router::Router;
use std::env;
@@ -22,27 +26,34 @@
Controller logic is defined in the web_handlers module.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-/// Prepares an Iron handler.
-fn initialize_handler() -> Router {
+/// Prepares an Iron handler to route requests.
+fn get_router() -> Router {
router!(
index: get "/" => ::web_handlers::get,
status: get "/status" => ::web_handlers::alive
)
}
+/// Prepares an Iron handler.
+fn initialize_handler(pool: SqlitePool) -> Chain {
+ let mut chain = Chain::new(get_router());
+ chain.link(Read::<SqliteDB>::both(pool));
+
+ chain
+}
+
/// Gets the address the HTTP server should bind to.
fn get_bind_address() -> String {
env::var("BIND").unwrap_or("0.0.0.0:3000".to_string())
}
/// Runs an HTTP server.
-pub fn run() {
+pub fn run(pool: SqlitePool) {
let listen = get_bind_address();
- info!("Starting HTTP server");
info!("Listen to http://{}", listen);
- let _server = Iron::new(initialize_handler()).http(&*listen);
+ let _server = Iron::new(initialize_handler(pool)).http(&*listen);
if let Err(err) = _server {
error!("{}", err)
}
@@ -57,7 +68,7 @@
extern crate iron_test;
use super::get_bind_address;
- use super::initialize_handler;
+ use super::get_router;
use iron::Headers;
use iron::status;
@@ -73,7 +84,7 @@
fn status_returns_200_alive() {
let response = request::get("http://localhost:3000/status",
Headers::new(),
- &initialize_handler())
+ &get_router())
.expect("Can't get a request.");
assert_eq!(response.status, Some(status::Ok));
diff --git a/src/main.rs b/src/main.rs
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,21 +13,30 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#[macro_use]
+extern crate lazy_static;
+
+#[macro_use]
extern crate log;
extern crate env_logger;
+extern crate num_cpus;
+
+extern crate r2d2;
+extern crate r2d2_sqlite;
+
+extern crate rusqlite;
+
extern crate iron;
+extern crate persistent;
#[macro_use]
extern crate router;
-extern crate sqlite3;
-
/* -------------------------------------------------------------
Modules
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-mod http_server;
mod store;
+mod http_server;
mod web_handlers;
/* -------------------------------------------------------------
@@ -42,5 +51,9 @@
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"));
- http_server::run();
+ info!("Preparing SQLite pool");
+ let pool = ::store::get_pool();
+
+ info!("Starting HTTP server");
+ http_server::run(pool);
}
diff --git a/src/store.rs b/src/store.rs
--- a/src/store.rs
+++ b/src/store.rs
@@ -6,15 +6,17 @@
License: BSD-2-Clause
------------------------------------------------------------- */
-use sqlite3::DatabaseConnection;
-use sqlite3::SqliteResult;
-use sqlite3::StatementUpdate;
-use sqlite3::ToSql;
-use sqlite3::access::ByFilename;
+use iron::typemap::Key;
+use r2d2;
+use r2d2_sqlite::SqliteConnectionManager;
+
+use rusqlite::types::ToSql;
+use rusqlite::Error as DBError;
+
+
+use std::error::Error;
use std::env;
-use std::fs;
-use std::io;
/* -------------------------------------------------------------
Log entry
@@ -29,61 +31,97 @@
}
/* -------------------------------------------------------------
+ SQLite pool
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub type SqlitePool = r2d2::Pool<SqliteConnectionManager>;
+pub type SqliteConnection = r2d2::PooledConnection<SqliteConnectionManager>;
+
+pub struct SqliteDB;
+impl Key for SqliteDB { type Value = SqlitePool;}
+
+/// Gets the size of the pool.
+pub fn get_pool_size() -> usize {
+ 8 * ::num_cpus::get() + 1
+}
+
+/// Gets a pool of SQLite connections.
+/// The pool size should match the number of thread of the HTTP server + 1 for the broker thread.
+pub fn get_pool() -> SqlitePool {
+ let size = get_pool_size() as u32;
+ let config = r2d2::Config::builder().pool_size(size).build();
+ let manager = SqliteConnectionManager::new(&*get_path());
+
+ r2d2::Pool::new(config, manager).unwrap()
+}
+
+/* -------------------------------------------------------------
Data store context
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
pub struct DataStore {
- /// The path to the SQLite database filename
- /// By default, honours $STORE environment variable, or if omitted, "./log.db".
- filename: String,
-
/// The connexion to the database
- connection: DatabaseConnection,
+ connection: SqliteConnection,
}
impl DataStore {
/// Initializes a new instance of the DataStore object.
- pub fn new () -> Result<DataStore, String> {
- let path = ::store::get_path();
- match ::store::get_connection() {
- Ok(database_connection) => {
- let mut store = DataStore {
- filename: path,
- connection: database_connection
- };
- store.init();
- Ok(store)
- },
- Err(err) => Err(err.desc.to_string())
+ pub fn new (database_connection: SqliteConnection) -> DataStore {
+ DataStore {
+ connection: database_connection
}
}
/// Initializes the data store.
pub fn init(&mut self) {
- self.connection.exec(::store::get_schema()).unwrap();
+ info!("Writing database schema");
+ self.connection.execute(::store::get_schema(), &[]).unwrap();
}
- /// Destroys the data store.
- pub fn destroy (&self) -> Result<(), io::Error> {
- try!(fs::remove_file(&*self.filename));
- info!("Destroyed {}", self.filename);
- Ok(())
- }
+ /// Executes a query.
+ pub fn execute (&self, query: &str, params: &[&ToSql]) -> Result<i32, String> {
+ debug!("SQL query: {}", query);
- /// Executes a prepared statement.
- /// - query: the SQL query, each parameter replaced by $1, $2, etc.
- /// - parameters: the parameters to put in the query
- pub fn exec_prepared_statement (&self, query: &str, parameters: &[&ToSql]) -> Result<u64, String> {
- match self.connection.prepare(query).unwrap().update(parameters) {
+ match self.connection.execute(query, params) {
Ok(rows_updated) => Ok(rows_updated),
- Err(err) => Err(err.desc.to_string()),
+ Err(err) => {
+ let error = format!("{}", err.description());
+ error!("SQL error: {}", error);
+
+ Err(error)
+ }
}
}
+ /// Gets all the log entries.
+ pub fn select_all_log_entries (&self) -> Result<Vec<LogEntry>, DBError> {
+ self.select_all(
+ "SELECT date, emitter, source, component, entry FROM person"
+ )
+ }
+
+ /// Runs a SELECT query, then maps rows as LogEntry.
+ fn select_all (&self, query: &str) -> Result<Vec<LogEntry>, DBError> {
+ let mut statement = try!(self.connection.prepare(query));
+
+ let rows = try!(statement.query_map(&[], |row|
+ LogEntry {
+ date: row.get(0),
+ emitter: row.get(1),
+ source: row.get(2),
+ component: row.get(3),
+ entry: row.get(4),
+ }
+ ));
+
+ rows.collect()
+ }
+
+
/// Inserts an entry to the log.
- pub fn insert (&self, entry: LogEntry) -> Result<u64, String> {
+ pub fn insert (&self, entry: LogEntry) -> Result<i32, String> {
debug!("Inserting new log entry");
- self.exec_prepared_statement(
+ self.execute(
"INSERT INTO log (date, emitter, source, component, entry)
VALUES ($1, $2, $3, $4, $5);",
&[&entry.date, &entry.emitter, &entry.source, &entry.component, &entry.entry]
@@ -92,31 +130,14 @@
}
/* -------------------------------------------------------------
- Helper functions — Database
+ Helper functions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-/// Opens a connection to the database.
-fn get_connection () -> SqliteResult<DatabaseConnection> {
- let path = &*get_path();
- info!("Opening database {}", path);
-
- DatabaseConnection::new(
- ByFilename {
- filename: path,
- flags: Default::default(),
- }
- )
-}
-
/// Gets the SQL tables schema for the log store.
pub fn get_schema<'a> () -> &'a str {
include_str!("../sql/schema.sql")
}
-/* -------------------------------------------------------------
- Helper functions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
/// Gets the path to the store to use.
fn get_path() -> String {
env::var("STORE").unwrap_or("log.db".to_string())
@@ -165,12 +186,12 @@
}
#[test]
- fn store_exists_when_initialized_but_not_after_destroy() {
+ fn store_exists_when_initialized() {
+ /*
let store = DataStore::new().unwrap();
+ store.init();
assert_eq!(true, store_exists(), "Store doesn't exist after initialization.");
-
- store.destroy().unwrap();
- assert_eq!(false, store_exists(), "Store still exists after being destroyed.");
+ */
}
//
@@ -179,6 +200,7 @@
#[test]
fn insert_adds_a_row() {
+ /*
let store = DataStore::new().unwrap();
let rows_updated = store.insert(
LogEntry {
@@ -192,6 +214,7 @@
assert_eq!(1, rows_updated);
store.destroy().unwrap();
+ */
}
}
diff --git a/src/web_handlers.rs b/src/web_handlers.rs
--- a/src/web_handlers.rs
+++ b/src/web_handlers.rs
@@ -9,6 +9,29 @@
use iron::prelude::*;
use iron::status;
+use ::store::DataStore;
+
+/* -------------------------------------------------------------
+ Database
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// Gets a connection from the pool from the given request or returns a 500
+macro_rules! get_store {
+ ($req:expr) => (match $req.get::<::persistent::Read<::store::SqliteDB>>() {
+ Ok(pool) => match pool.get() {
+ Ok(connection) => DataStore::new(connection),
+ Err(_) => {
+ error!("Can't get from the pool a database connection.");
+ return Ok(Response::with((status::InternalServerError)));
+ }
+ },
+ Err(_) => {
+ error!("Can't get from the pool a database connection.");
+ return Ok(Response::with((status::InternalServerError)));
+ }
+ })
+}
+
/* -------------------------------------------------------------
Handlers for web requests
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -20,6 +43,9 @@
/// Serves the log as a JSON document.
/// Serves a 503 reply as it's not implemented yet.
-pub fn get(_: &mut Request) -> IronResult<Response> {
+pub fn get(request: &mut Request) -> IronResult<Response> {
+ let store = get_store!(request);
+ let entries = store.select_all_log_entries();
+
Ok(Response::with((status::ServiceUnavailable, "Once upon a time, a log was baked.\n")))
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jul 27, 14:35 (7 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2849753
Default Alt Text
D712.id1835.diff (12 KB)
Attached To
Mode
D712: WIP: Allow HTTP server to consume SQLite
Attached
Detach File
Event Timeline
Log In to Comment