Browse Source

Added support of sqlite database as

efficient session storage
master
Artyom Beilis 12 years ago
parent
commit
03aaf877b0
7 changed files with 668 additions and 225 deletions
  1. +11
    -1
      CMakeLists.txt
  2. +2
    -0
      config.cmake.h
  3. +0
    -216
      contrib/server_side/sqlite3_storage.cpp
  4. +38
    -0
      private/session_sqlite_storage.h
  5. +22
    -6
      src/session_pool.cpp
  6. +537
    -0
      src/session_sqlite_storage.cpp
  7. +58
    -2
      tests/storage_test.cpp

+ 11
- 1
CMakeLists.txt View File

@@ -29,6 +29,7 @@ option(DISABLE_HTTP "Disable http web server" OFF)
option(DISABLE_CACHE "Disable cache beckend" OFF)
option(DISABLE_TCPCACHE "Disable distributed cache beckend" OFF)
option(DISABLE_GZIP "Disable support of gzip output compression" OFF)
option(STATIC_SQLITE "Link with sqlite library directly, do not load it" OFF)

if(CMAKE_CXX_COMPILER_ID STREQUAL "SunPro")
option(DISABLE_PREFORK_CACHE "Disable prefork-cache beckend" ON)
@@ -326,6 +327,7 @@ set(CPPCMS_SOURCES
src/session_sid.cpp
src/session_interface.cpp
src/session_memory_storage.cpp
src/session_sqlite_storage.cpp
src/rpc_json.cpp
src/mount_point.cpp
src/archive.cpp
@@ -377,7 +379,12 @@ if(NOT LIB_CRYPT)
message("GNU-TLS Gcrypt or OpenSSL librarys are not found, disabling AES encryption support")
endif()


if(STATIC_SQLITE)
set(CPPCMS_SQLITE_LINK_STATIC 1)
find_library(LIB_SQLITE sqlite3)
find_path(HEADER_SQLITE sqlite3.h)
include_directories(${HEADER_SQLITE})
endif()

if(NOT DISABLE_FCGI)
set(CPPCMS_SOURCES ${CPPCMS_SOURCES} src/fastcgi_api.cpp)
@@ -453,6 +460,9 @@ foreach(ALIB ${CPPCMS_LIBS})
if(NOT DISABLE_GZIP)
target_link_libraries(${ALIB} ${ZLIB})
endif()
if(STATIC_SQLITE)
target_link_libraries(${ALIB} ${LIB_SQLITE})
endif()
endforeach(ALIB)




+ 2
- 0
config.cmake.h View File

@@ -119,5 +119,7 @@
#cmakedefine CPPCMS_NO_CACHE
#cmakedefine CPPCMS_NO_PREFOK_CACHE
#cmakedefine CPPCMS_NO_GZIP
#cmakedefine CPPCMS_SQLITE_LINK_STATIC


#endif

+ 0
- 216
contrib/server_side/sqlite3_storage.cpp View File

@@ -1,216 +0,0 @@
#include <cppdb/frontend.h>
#include <cppcms/session_storage.h>
#include <booster/thread.h>
#include <sstream>
#include <map>

namespace {

class sql_session_storage : public cppcms::sessions::session_storage
{
struct data {
time_t timeout;
std::string value;
data() : timeout(0) {}
data(time_t t,std::string const &v) : timeout(t), value(v) {}
};

typedef std::map<std::string,data> data_type;
typedef booster::unique_lock<booster::shared_mutex> unique_guard;
typedef booster::unique_lock<booster::shared_mutex> shared_guard;

data_type data_;
booster::shared_mutex lock_;
data_type data_in_write_;
booster::shared_mutex data_in_write_lock_;
booster::thread_specific_ptr<cppdb::session> sql_;
std::string conn_;

public:
sql_session_storage(std::string const &conn) : conn_(conn)
{
}

virtual void save(std::string const &sid,time_t timeout,std::string const &in)
{
unique_guard g(lock_);
data_[sid]=data(timeout,in);
}

///
/// Load session with \a sid, put its end of life time to \a timeout and return its
/// value to \a out
///
virtual bool load(std::string const &sid,time_t &timeout,std::string &out)
{
shared_guard g(lock_);
data_type::iterator p=data_.find(sid);
if(p!=data_.end()) {
if(p->second.timeout < time(0))
return false;
timeout = p->second.timeout;
out = p->second.value;
return true;
}

{
shared_guard g2(data_in_write_lock_);

p=data_in_write_.find(sid);
if(p!=data_in_write_.end()) {
if(p->second.timeout < time(0))
return false;
timeout = p->second.timeout;
out = p->second.value;
return true;
}
}

if(sql_.get()==0) {
sql_.reset(new cppdb::session(conn_ + ";mode=readonly"));
sql_->prepare("PRAGMA read_uncommited=true").exec();
}
cppdb::session &sql = *sql_;
cppdb::result r;
r= sql << "SELECT timeout,data FROM sessions "
"WHERE sid = ?" << sid << cppdb::row;
if(r.empty())
return false;
time_t t;
std::stringstream ss;
r >> t;
if(t < time(0))
return false;
r >> ss;
out = ss.str();
timeout = t;
return true;
}
virtual void remove(std::string const &sid)
{
unique_guard g(lock_);
data_[sid]=data();
}

virtual bool is_blocking()
{
return true;
}

void gc()
{
{
unique_guard g(lock_);
{
unique_guard g2(data_in_write_lock_);
data_in_write_.swap(data_);
}
}
{
shared_guard g2(data_in_write_lock_);
cppdb::session sql(conn_);
cppdb::transaction tr(sql);
int count = 1;
for(data_type::iterator p=data_.begin();p!=data_.end();++p) {
if(p->second.timeout == 0)
sql << "DELETE from sessions WHERE sid=?" << p->first << cppdb::exec;
else {
std::stringstream ss(p->second.value);
sql << "REPLACE INTO sessions values(?,?,?)"
<< p->first << p->second.timeout << ss << cppdb::exec;
}
count ++;
}
sql << "DELETE FROM sessions WHERE timeout < ? limit ?" << time(0) << (count * 5) << cppdb::exec;
tr.commit();
}

{
unique_guard g2(data_in_write_lock_);
data_in_write_.clear();
}
}
private:
};



///
/// \brief The factory is an interface to a factory that creates session_storage objects, it should be thread safe.
///
class factory : public cppcms::sessions::session_storage_factory {
public:
factory(std::string const &file)
{
std::string conn_str = "sqlite3:db='" + file +"'";
cppdb::session sql(conn_str);
sql << "CREATE TABLE IF NOT EXISTS "
"sessions ( "
" sid varchar(32) primary key not null,"
" timeout integer not null, "
" data blob not null"
")"
<< cppdb::exec;
sql << "CREATE INDEX IF NOT EXISTS "
"sessions_timeout on sessions(timeout) "
<< cppdb::exec;
sql.close();
storage_.reset(new sql_session_storage(conn_str));

}
///
/// Get a pointer to session_storage. Note if the returned pointer is same for different calls
/// session_storage implementation should be thread safe.
///
virtual booster::shared_ptr<session_storage> get()
{
return storage_;
}

///
/// Return true if session_storage requires garbage collection - removal of expired session time-to-time
///
virtual bool requires_gc()
{
return true;
}
virtual void gc_job()
{
storage_->gc();
}
///
/// Delete the object, cleanup
///
virtual ~factory()
{
try {
storage_->gc();
}
catch(...) {}
}
private:
booster::shared_ptr<sql_session_storage> storage_;
};




} // anonymous


extern "C" {

cppcms::sessions::session_storage_factory *cppdb_session_storage(std::string const &parameter)
{
return new factory(parameter);
}

} // extern "C"



+ 38
- 0
private/session_sqlite_storage.h View File

@@ -0,0 +1,38 @@
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2010 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCMS_PRIVATE_SESSION_SQLITE_H
#define CPPCMS_PRIVATE_SESSION_SQLITE_H

#include <cppcms/defs.h>
#include <string>

namespace cppcms {
namespace sessions {
class session_storage_factory;
namespace sqlite_session {

CPPCMS_API session_storage_factory *factory(std::string const &database,std::string const &shared_object);
CPPCMS_API session_storage_factory *factory(std::string const &database);


}// sqlite_session
}// sessions
}// cppcms

#endif

+ 22
- 6
src/session_pool.cpp View File

@@ -44,6 +44,7 @@
#include "session_posix_file_storage.h"
#endif
#include "session_memory_storage.h"
#include "session_sqlite_storage.h"

#include <booster/aio/deadline_timer.h>
#include <booster/callback.h>
@@ -129,7 +130,7 @@ private:

class session_pool::gc_job : public booster::enable_shared_from_this<gc_job> {
public:
gc_job(service *ser,int freq,session_pool *pool) :
gc_job(service *ser,double freq,session_pool *pool) :
timer_(new booster::aio::deadline_timer(ser->get_io_service())),
service_(ser),
freq_(freq),
@@ -143,13 +144,15 @@ public:
private:
void gc() const
{
booster::ptime start = booster::ptime::now();
booster::ptime restart = start + booster::ptime::from_number(freq_);
pool_->backend_->gc();
timer_->expires_from_now(booster::ptime(freq_));
timer_->expires_at(restart);
timer_->async_wait(boost::bind(&gc_job::async_run,shared_from_this()));
}
booster::shared_ptr<booster::aio::deadline_timer> timer_;
service *service_;
int freq_;
double freq_;
session_pool *pool_;
};

@@ -162,7 +165,7 @@ void session_pool::init()

std::string location=srv.settings().get("session.location","none");

if(location == "client" || (location=="both" && !encryptor_.get())) {
if((location == "client" || location=="both") && !encryptor_.get()) {
using namespace cppcms::sessions::impl;
std::string enc=srv.settings().get("session.client.encryptor","");
std::string mac=srv.settings().get("session.client.hmac","");
@@ -228,7 +231,7 @@ void session_pool::init()

encryptor(factory);
}
if(location == "server" || (location == "both" && !storage_.get())) {
if((location == "server" || location == "both") && !storage_.get()) {
std::string stor=srv.settings().get<std::string>("session.server.storage");
std::auto_ptr<sessions::session_storage_factory> factory;
if(stor == "files") {
@@ -248,6 +251,19 @@ void session_pool::init()
throw cppcms_error("Can't use memory storage with more then 1 worker process");
factory.reset(new session_memory_storage_factory());
}
else if(stor == "sqlite") {
if(srv.procs_no() > 1)
throw cppcms_error("Can't use sqlite storage with more then 1 worker process");
if(srv.settings().get("session.gc",0.0) <= 0) {
throw cppcms_error("The value of session.gc should be small positive value - like 1.0 - one second");
}
std::string db = srv.settings().get<std::string>("session.server.sqlite.db");
std::string so = srv.settings().get("session.server.sqlite.so","");
if(so.empty())
factory.reset(sqlite_session::factory(db));
else
factory.reset(sqlite_session::factory(db,so));
}
else
throw cppcms_error("Unknown server side storage:"+stor);
storage(factory);
@@ -283,7 +299,7 @@ void session_pool::after_fork()
if(backend_.get() && backend_->requires_gc()) {
if(service_->process_id()!=1)
return;
int frequency = service_->settings().get("session.gc",0);
double frequency = service_->settings().get("session.gc",0.0);
if(frequency > 0) {
booster::shared_ptr<gc_job> job(new gc_job(service_,frequency,this));
job->async_run();


+ 537
- 0
src/session_sqlite_storage.cpp View File

@@ -0,0 +1,537 @@
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2010 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#include <cppcms/config.h>
#include <cppcms/session_storage.h>
#include <booster/thread.h>
#include <booster/backtrace.h>
#include <sstream>
#include <iostream>
#include <map>
#include <time.h>

#ifdef CPPCMS_SQLITE_LINK_STATIC
#include <sqlite3.h>
namespace cppcms { namespace sessions { namespace sqlite_session {namespace details {
void load_sqlite(char const *) {}
}}}}
#else // dynamic loading

#ifdef CPPCMS_WIN_NATIVE
#include <booster/nowide/convert.h>
#include <windows.h>
#else
#include <dlfcn.h>
#endif


extern "C" {

// API We need and we can DLOPEN

#define SQLITE_OK 0
#define SQLITE_BUSY 5
#define SQLITE_LOCKED 6
#define SQLITE_DONE 101
#define SQLITE_ROW 100
#define SQLITE_STATIC ((void (*)(void*))0)

typedef long long int sqlite3_int64;
typedef struct sqlite3 sqlite3;
typedef struct sqlite3_stmt sqlite3_stmt;
static int (*sqlite3_finalize)(sqlite3_stmt *);
static int (*sqlite3_bind_text)(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
static int (*sqlite3_step)(sqlite3_stmt*);
static int (*sqlite3_bind_int64)(sqlite3_stmt*, int, sqlite3_int64);
static int (*sqlite3_bind_int)(sqlite3_stmt*, int, int);
static int (*sqlite3_bind_blob)(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
static sqlite3_int64 (*sqlite3_column_int64)(sqlite3_stmt*, int);
static const void *(*sqlite3_column_blob)(sqlite3_stmt*, int);
static int (*sqlite3_column_bytes)(sqlite3_stmt*, int);
static const char *(*sqlite3_errmsg)(sqlite3*);
static int (*sqlite3_reset)(sqlite3_stmt *);
static int (*sqlite3_clear_bindings)(sqlite3_stmt*);
static int (*sqlite3_close)(sqlite3 *);
static int (*sqlite3_open)(char const *,sqlite3 **);
static int (*sqlite3_prepare_v2)(sqlite3 *,char const *,int,sqlite3_stmt **,char const **);
static int (*sqlite3_libversion_number)();

} // extern "C"

namespace cppcms {
namespace sessions {
namespace sqlite_session {
namespace details {
#ifdef CPPCMS_WIN_NATIVE
#define RTLD_LAZY 0
typedef HMODULE handle_type;
handle_type dlopen(char const *x,int) { return LoadLibraryW(booster::nowide::convert(x).c_str()); }
void dlclose(handle_type x) { FreeLibrary(x); }
void *dlsym(handle_type x,char const *y) { return (void*)(GetProcAddress(x,y)); }
#else
typedef void *handle_type;
#endif

class sqlite_loader {
public:


sqlite_loader() : loaded(false),handle(0)
{
}
~sqlite_loader()
{
if(handle) {
dlclose(handle);
}
}

void load(char const *name)
{
if(loaded)
return;
try {
open(name);
load_sqlite_so();
}
catch(...) {
if(handle) {
dlclose(handle);
handle = 0;
}
}
loaded = true;
}
bool loaded;
private:

template<typename T>
void load_symbol(T &sym,char const *name)
{
void *p=dlsym(handle,name);
if(!p) {
throw std::runtime_error(std::string("Failed to resolve ") + name);
}
sym = (T)(p);
}
void open(char const *name)
{
handle=dlopen(name,RTLD_LAZY);
if(!handle) {
throw std::runtime_error(std::string("Failed to load library:") + name);
}
}
void load_sqlite_so()
{
#define LOAD(x) load_symbol(x,#x)
LOAD(sqlite3_finalize);
LOAD(sqlite3_bind_text);
LOAD(sqlite3_step);
LOAD(sqlite3_bind_int64);
LOAD(sqlite3_bind_int);
LOAD(sqlite3_bind_blob);
LOAD(sqlite3_column_int64);
LOAD(sqlite3_column_blob);
LOAD(sqlite3_column_bytes);
LOAD(sqlite3_errmsg);
LOAD(sqlite3_reset);
LOAD(sqlite3_clear_bindings);
LOAD(sqlite3_close);
LOAD(sqlite3_open);
LOAD(sqlite3_prepare_v2);
LOAD(sqlite3_libversion_number);
}

handle_type handle;

} sqlite_loader_instance;

booster::mutex instance_lock;
void load_sqlite(char const *name)
{
if(sqlite_loader_instance.loaded)
return;
booster::unique_lock<booster::mutex> g(instance_lock);
sqlite_loader_instance.load(name);
}
} // details
} // sqlite_session
} // sessions
} // cppcms

#endif

namespace cppcms {
namespace sessions {
namespace sqlite_session {


class sql_object {
public:
struct st_guard {
st_guard(sqlite3_stmt *st) : st_(st) {}
~st_guard()
{
sqlite3_clear_bindings(st_);
sqlite3_reset(st_);
}
private:
sqlite3_stmt *st_;
};
sql_object(std::string const &db_name) :
conn_(0),
replace_(0),
remove_(0),
cleanup_(0),
load_(0)
{
if(sqlite3_open(db_name.c_str(),&conn_)!=SQLITE_OK) {
if(conn_==0)
throw std::bad_alloc();
std::string msg = sqlite3_errmsg(conn_);
sqlite3_close(conn_);
throw booster::runtime_error(msg);
}
}
void begin()
{
exec("BEGIN");
}
void commit()
{
exec("COMMIT");
}
void prepare(sqlite3_stmt *&st,char const *stmt)
{
if(st)
return;
int r;
while((r=sqlite3_prepare_v2(conn_,stmt,-1,&st,0))==SQLITE_LOCKED || r==SQLITE_BUSY)
;
if(r!=SQLITE_OK)
throw_error();
}
void replace(std::string const &sid,time_t timeout,std::string const &data)
{
prepare(replace_,"REPLACE INTO sessions values(?,?,?)");
st_guard g(replace_);
check(sqlite3_bind_text(replace_,1,sid.c_str(),sid.size(),SQLITE_STATIC)==SQLITE_OK);
check(sqlite3_bind_int64(replace_,2,timeout)==SQLITE_OK);
check(sqlite3_bind_blob(replace_,3,data.c_str(),data.size(),SQLITE_STATIC)==SQLITE_OK);
check(sqlite3_step(replace_)==SQLITE_DONE);
}
void remove(std::string const &sid)
{
prepare(remove_,"DELETE FROM sessions WHERE sid=?");
st_guard g(remove_);
check(sqlite3_bind_text(remove_,1,sid.c_str(),sid.size(),SQLITE_STATIC)==SQLITE_OK);
check(sqlite3_step(remove_)==SQLITE_DONE);
}
void cleanup(time_t time,int limit)
{
prepare(cleanup_,
"DELETE FROM sessions "
"WHERE sid in "
"("
" SELECT sid FROM sessions "
" WHERE timeout < ? LIMIT ? "
")"
);
st_guard g(cleanup_);
check(sqlite3_bind_int64(cleanup_,1,time)==SQLITE_OK);
check(sqlite3_bind_int(cleanup_,2,limit)==SQLITE_OK);
check(sqlite3_step(cleanup_)==SQLITE_DONE);
}
bool load(std::string const &sid,time_t &to,std::string &value)
{
prepare(load_,"SELECT timeout,data FROM sessions WHERE sid=?");
st_guard g(load_);
check(sqlite3_bind_text(load_,1,sid.c_str(),sid.size(),SQLITE_STATIC)==SQLITE_OK);
for(;;) {
int r = sqlite3_step(load_);
if(r == SQLITE_DONE)
return false;
if(r == SQLITE_BUSY || r==SQLITE_LOCKED ) {
sqlite3_reset(load_);
continue;
}
if(r != SQLITE_ROW)
throw_error();
break;
}
time_t got_to = sqlite3_column_int64(load_,0);
if(got_to < time(0))
return false;
to = got_to;
char const *data = static_cast<char const *>(sqlite3_column_blob(load_,1));
size_t size = sqlite3_column_bytes(load_,1);
value.assign(data,size);
return true;
}
void exec(char const *stmt)
{
sqlite3_stmt *st = 0;
prepare(st,stmt);
int r = sqlite3_step(st);
sqlite3_finalize(st);
if(r!=SQLITE_DONE && r!=SQLITE_ROW) {
throw_error();
}
}
~sql_object()
{
finalize(replace_);
finalize(remove_);
finalize(cleanup_);
finalize(load_);
sqlite3_close(conn_);
}

private:
void check(bool c)
{
if(!c)
throw_error();
}
void finalize(sqlite3_stmt *&st)
{
if(st) {
sqlite3_finalize(st);
st = 0;
}
}
void throw_error()
{
throw booster::runtime_error(sqlite3_errmsg(conn_));
}

sqlite3 *conn_;
sqlite3_stmt *replace_;
sqlite3_stmt *remove_;
sqlite3_stmt *cleanup_;
sqlite3_stmt *load_;
};

class sql_session_storage : public cppcms::sessions::session_storage
{
struct data {
time_t timeout;
std::string value;
data() : timeout(0) {}
data(time_t t,std::string const &v) : timeout(t), value(v) {}
};

typedef std::map<std::string,data> data_type;
typedef booster::unique_lock<booster::shared_mutex> unique_guard;
typedef booster::shared_lock<booster::shared_mutex> shared_guard;

data_type data_;
booster::shared_mutex lock_;
data_type data_in_write_;
booster::shared_mutex data_in_write_lock_;
booster::thread_specific_ptr<sql_object> sql_;
std::string conn_;

public:
sql_session_storage(std::string const &conn) : conn_(conn)
{
}

virtual void save(std::string const &sid,time_t timeout,std::string const &in)
{
unique_guard g(lock_);
data_[sid]=data(timeout,in);
}

///
/// Load session with \a sid, put its end of life time to \a timeout and return its
/// value to \a out
///
virtual bool load(std::string const &sid,time_t &timeout,std::string &out)
{
shared_guard g(lock_);
data_type::iterator p=data_.find(sid);
if(p!=data_.end()) {
if(p->second.timeout < time(0))
return false;
timeout = p->second.timeout;
out = p->second.value;
return true;
}

{
shared_guard g2(data_in_write_lock_);

p=data_in_write_.find(sid);
if(p!=data_in_write_.end()) {
if(p->second.timeout < time(0))
return false;
timeout = p->second.timeout;
out = p->second.value;
return true;
}
}

if(sql_.get()==0) {
sql_.reset(new sql_object(conn_));
}
return sql_->load(sid,timeout,out);
}
virtual void remove(std::string const &sid)
{
unique_guard g(lock_);
data_[sid]=data();
}

virtual bool is_blocking()
{
return true;
}

void gc()
{
{
unique_guard g(lock_);
{
unique_guard g2(data_in_write_lock_);
data_in_write_.swap(data_);
}
}
{
shared_guard g2(data_in_write_lock_);
sql_object sql(conn_);
sql.begin();
try {
int count = 0;
for(data_type::iterator p=data_in_write_.begin();p!=data_in_write_.end();++p) {
if(p->second.timeout == 0)
sql.remove(p->first);
else
sql.replace(p->first,p->second.timeout,p->second.value);
count ++;
}
sql.cleanup(time(0),((count * 5) + 1000));
sql.commit();
}
catch(...) {
try { sql.exec("ROLLBACK"); }catch(...){}
throw;
}
}

{
unique_guard g2(data_in_write_lock_);
data_in_write_.clear();
}
}
private:
};



///
/// \brief The factory is an interface to a factory that creates session_storage objects, it should be thread safe.
///
class factory_object : public cppcms::sessions::session_storage_factory {
public:
factory_object(std::string const &file,std::string const &so)
{
details::load_sqlite(so.c_str());
{
sql_object sql(file);
if(sqlite3_libversion_number() < (3*1000000 + 7*1000)) {
throw booster::runtime_error("Sqlite 3.7 and above required");
}
// Very Important
sql.exec("PRAGMA journal_mode = WAL");
sql.exec("CREATE TABLE IF NOT EXISTS "
"sessions ( "
" sid varchar(32) primary key not null,"
" timeout integer not null, "
" data blob not null"
")");
sql.exec("CREATE INDEX IF NOT EXISTS "
"sessions_timeout on sessions(timeout) ");
}
storage_.reset(new sql_session_storage(file));

}
///
/// Get a pointer to session_storage. Note if the returned pointer is same for different calls
/// session_storage implementation should be thread safe.
///
virtual booster::shared_ptr<cppcms::sessions::session_storage> get()
{
return storage_;
}

///
/// Return true if session_storage requires garbage collection - removal of expired session time-to-time
///
virtual bool requires_gc()
{
return true;
}
virtual void gc_job()
{
storage_->gc();
}
///
/// Delete the object, cleanup
///
virtual ~factory_object()
{
try {
storage_->gc();
}
catch(...) {}
}
private:
booster::shared_ptr<sql_session_storage> storage_;
};



CPPCMS_API session_storage_factory *factory(std::string const &database,std::string const &shared_object)
{
return new cppcms::sessions::sqlite_session::factory_object(database,shared_object);
}

CPPCMS_API session_storage_factory *factory(std::string const &database)
{
#ifndef CPPCMS_LIBRARY_PREFIX
#define CPPCMS_LIBRARY_PREFIX ""
#endif
return factory(database,CPPCMS_LIBRARY_PREFIX "sqlite3" CPPCMS_LIBRARY_SUFFIX);
}

}// sqlite_session
}// sessions
}// cppcms



+ 58
- 2
tests/storage_test.cpp View File

@@ -20,6 +20,7 @@
#include "test.h"
#include <cppcms/session_storage.h>
#include "session_memory_storage.h"
#include "session_sqlite_storage.h"
#ifdef CPPCMS_WIN_NATIVE
#include "session_win32_file_storage.h"
#include <windows.h>
@@ -28,38 +29,56 @@
#include <sys/types.h>
#include <dirent.h>
#endif
#include <booster/function.h>
#include <string.h>
#include <memory>
#include <iostream>
#include <vector>
#include <stdio.h>
#include <time.h>


std::string dir = "./sessions";
std::string bs="0123456789abcdef0123456789abcde";

void test(booster::shared_ptr<cppcms::sessions::session_storage> storage)
void do_nothing() {}

void test(booster::shared_ptr<cppcms::sessions::session_storage> storage,booster::function<void()> callback=do_nothing)
{
time_t now=time(0)+3;
callback();
storage->save(bs+"1",now,"");
std::string out="xx";
time_t tout;
callback();
TEST(storage->load(bs+"1",tout,out));
TEST(out.empty());
TEST(tout==now);
callback();
storage->remove(bs+"1");
callback();
TEST(!storage->load(bs+"1",tout,out));
callback();
storage->save(bs+"1",now-4,"hello world");
callback();
TEST(!storage->load(bs+"1",tout,out));
callback();
storage->save(bs+"1",now,"hello world");
callback();
TEST(storage->load(bs+"1",tout,out));
callback();
TEST(out=="hello world");
storage->save(bs+"2",now,"x");
callback();
storage->remove(bs+"2");
callback();
TEST(storage->load(bs+"1",tout,out));
TEST(out=="hello world");
callback();
storage->remove(bs+"1");
callback();
storage->remove(bs+"2");
callback();
}

int count_files()
@@ -118,10 +137,22 @@ void test_files(booster::shared_ptr<cppcms::sessions::session_storage> storage,
}


struct do_gc {
cppcms::sessions::session_storage_factory *f;
int n;
void operator()() const
{
for(int i=0;i<n;i++) {
f->gc_job();
}
}
};

int main()
{
try {
booster::shared_ptr<cppcms::sessions::session_storage> storage;
std::auto_ptr<cppcms::sessions::session_storage_factory> storage_factory;
using namespace cppcms::sessions;

std::cout << "Testing memory storage" << std::endl;
@@ -147,6 +178,31 @@ int main()
storage=f.get();
test_files(storage,f);
#endif

std::cout << "Testing sqlite storage" << std::endl;
remove("test.db");
try {
storage_factory.reset(cppcms::sessions::sqlite_session::factory("test.db"));
}
catch(std::exception const &e) {
std::string msg = e.what();
if( msg.find("Failed to load library")!=std::string::npos
|| msg.find("3.7 and above required")!=std::string::npos)
{
std::cout << "Seems that sqlite3 storage not supported" << std::endl;
}
else
throw;
}
if(storage_factory.get()) {
storage=storage_factory->get();
for(int i=0;i<3;i++) {
std::cout << "- GC " << i << std::endl;
do_gc gc = { storage_factory.get(), i};
test(storage,gc);
}
}
}
catch(std::exception const &e) {
std::cerr <<"Fail" << e.what() << std::endl;


Loading…
Cancel
Save