Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3762774
D712.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D712.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::initialize_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,12 +6,15 @@
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;
@@ -29,99 +32,153 @@
}
/* -------------------------------------------------------------
+ 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.
+fn get_pool_size() -> usize {
+ 8 * ::num_cpus::get() + 1
+}
+
+fn get_pool_of_size(size: u32) -> SqlitePool {
+ let config = r2d2::Config::builder().pool_size(size).build();
+ let manager = SqliteConnectionManager::new(&*get_path());
+
+ r2d2::Pool::new(config, manager).unwrap()
+}
+
+/// 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;
+ get_pool_of_size(size)
+}
+
+/// Initializes a SQLite database and a pool of connections.
+pub fn initialize_pool() -> SqlitePool {
+ let pool = get_pool();
+
+ let connection = pool.get().unwrap();
+ let mut store = DataStore::new(connection);
+ store.init();
+
+ pool
+}
+
+/* -------------------------------------------------------------
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 {
+
+ /* -------------------------------------------------------------
+ Constructor
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
/// 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
}
}
+ /* -------------------------------------------------------------
+ Query helpers
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+ /// Executes a query.
+ fn execute (&self, query: &str, params: &[&ToSql]) -> Result<i32, String> {
+ debug!("SQL query: {}", query);
+
+ match self.connection.execute(query, params) {
+ Ok(rows_updated) => Ok(rows_updated),
+ Err(err) => {
+ let error = format!("{}", err.description());
+ error!("SQL error: {}", error);
+
+ Err(error)
+ }
+ }
+ }
+
+ /// 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()
+ }
+
+ /* -------------------------------------------------------------
+ CRUD methods
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
/// 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 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) {
- Ok(rows_updated) => Ok(rows_updated),
- Err(err) => Err(err.desc.to_string()),
- }
+ /// 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 log"
+ )
}
/// 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]
)
}
+
}
/* -------------------------------------------------------------
- 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())
}
+/// Destroys the data store
+fn destroy() -> io::Result<()> {
+ try!(fs::remove_file(get_path()));
+ Ok(())
+}
+
/* -------------------------------------------------------------
Tests
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -129,7 +186,9 @@
#[cfg(test)]
mod tests {
+ use super::destroy;
use super::get_path;
+ use super::get_pool_of_size;
use super::get_schema;
use super::DataStore;
use super::LogEntry;
@@ -151,25 +210,35 @@
}
//
- // SQLite file tests
+ // Helpers to interact with data store
//
+ /// Gets a DataStore
+ fn get_store () -> DataStore {
+ let connection = get_pool_of_size(2).get().unwrap();
+ DataStore::new(connection)
+ }
+
/// Determines if the store exists.
fn store_exists () -> bool {
Path::new(&get_path()).exists()
}
+ //
+ // SQLite file tests
+ //
+
#[test]
fn get_store_path_returns_expected_default_value() {
assert_eq!("log.db", get_path());
}
#[test]
- fn store_exists_when_initialized_but_not_after_destroy() {
- let store = DataStore::new().unwrap();
+ fn store_exists_when_initialized() {
+ get_store().init();
assert_eq!(true, store_exists(), "Store doesn't exist after initialization.");
- store.destroy().unwrap();
+ destroy().unwrap();
assert_eq!(false, store_exists(), "Store still exists after being destroyed.");
}
@@ -178,8 +247,10 @@
//
#[test]
- fn insert_adds_a_row() {
- let store = DataStore::new().unwrap();
+ fn we_can_insert_and_select_a_row() {
+ let mut store = get_store();
+ store.init();
+
let rows_updated = store.insert(
LogEntry {
date: String::from("2016-03-30T13:03:00Z"),
@@ -191,7 +262,10 @@
).unwrap();
assert_eq!(1, rows_updated);
- store.destroy().unwrap();
+ let entries = store.select_all_log_entries().unwrap();
+ assert_eq!(1, entries.len());
+
+ 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,30 @@
use iron::prelude::*;
use iron::status;
+use ::store::DataStore;
+
+/* -------------------------------------------------------------
+ Database
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// Gets a data store, with a connection from the pool
+/// from the given request or returns a 500 error.
+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 +44,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
Fri, Nov 22, 14:51 (7 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2256275
Default Alt Text
D712.diff (14 KB)
Attached To
Mode
D712: WIP: Allow HTTP server to consume SQLite
Attached
Detach File
Event Timeline
Log In to Comment