Browse Source

Merged: -r 583:606 from /framework/branches/sessions, full merge of sessions branch

master
Artyom Beilis 15 years ago
parent
commit
68856497b6
49 changed files with 2807 additions and 347 deletions
  1. +24
    -7
      Makefile.am
  2. +45
    -28
      aes_encryptor.cpp
  3. +10
    -5
      aes_encryptor.h
  4. +1
    -0
      application.cpp
  5. +4
    -2
      application.h
  6. +28
    -0
      archive.h
  7. +7
    -4
      cache_interface.cpp
  8. +2
    -2
      cache_interface.h
  9. +55
    -2
      config.txt
  10. +60
    -12
      configure.in
  11. +21
    -7
      cppcms.kdevelop
  12. +8
    -2
      cppcms_error.h
  13. +9
    -0
      cppcms_make_key
  14. +8
    -8
      encryptor.cpp
  15. +24
    -6
      hello_world.cpp
  16. +1
    -0
      hmac_encryptor.cpp
  17. +75
    -7
      manager.cpp
  18. +5
    -3
      manager.h
  19. +22
    -0
      session_api.h
  20. +18
    -0
      session_backend_factory.h
  21. +33
    -0
      session_cache_backend.h
  22. +90
    -0
      session_cookies.cpp
  23. +29
    -0
      session_cookies.h
  24. +18
    -20
      session_dbixx_storage.h
  25. +65
    -0
      session_dual.cpp
  26. +31
    -0
      session_dual.h
  27. +512
    -0
      session_file_storage.cpp
  28. +97
    -0
      session_file_storage.h
  29. +195
    -0
      session_interface.cpp
  30. +82
    -0
      session_interface.h
  31. +0
    -44
      session_server_storage_with_cache.cpp
  32. +0
    -36
      session_server_storage_with_cache.h
  33. +152
    -0
      session_sid.cpp
  34. +46
    -0
      session_sid.h
  35. +315
    -0
      session_sqlite_storage.cpp
  36. +44
    -0
      session_sqlite_storage.h
  37. +20
    -1
      session_storage.h
  38. +82
    -0
      session_tcp_storage.cpp
  39. +28
    -0
      session_tcp_storage.h
  40. +4
    -121
      tcp_cache.cpp
  41. +9
    -7
      tcp_cache.h
  42. +8
    -1
      tcp_cache_protocol.h
  43. +316
    -15
      tcp_cache_server.cpp
  44. +56
    -0
      tcp_connector.cpp
  45. +31
    -0
      tcp_connector.h
  46. +60
    -0
      tcp_messenger.cpp
  47. +44
    -0
      tcp_messenger.h
  48. +7
    -2
      worker_thread.cpp
  49. +6
    -5
      worker_thread.h

+ 24
- 7
Makefile.am View File

@@ -2,7 +2,7 @@ SUBDIRS = ./transtext

noinst_PROGRAMS = hello_world.fcgi
noinst_HEADERS = hello_world_view.h
dist_bin_SCRIPTS = cppcms_tmpl_cc cppcms_run
dist_bin_SCRIPTS = cppcms_tmpl_cc cppcms_run cppcms_make_key

hello_world_fcgi_SOURCES = hello_world.cpp hello_world_view1.cpp hello_world_view2.cpp
hello_world_fcgi_LDADD = libcppcms.la transtext/libcppcmstranstext.la
@@ -19,7 +19,9 @@ hello_world_view2.cpp: hello_world_skin2.tmpl hello_world_view1.tmpl
lib_LTLIBRARIES = libcppcms.la
libcppcms_la_SOURCES = global_config.cpp manager.cpp url.cpp worker_thread.cpp \
text_tool.cpp cache_interface.cpp base_cache.cpp thread_cache.cpp scgi.cpp \
base_view.cpp util.cpp form.cpp application.cpp
base_view.cpp util.cpp form.cpp application.cpp session_interface.cpp \
session_cookies.cpp hmac_encryptor.cpp encryptor.cpp md5.c base64.cpp \
session_sid.cpp session_file_storage.cpp session_dual.cpp

libcppcms_la_LDFLAGS = -no-undefined -version-info 0:0:0
libcppcms_la_LIBADD = @CPPCMS_LIBS@ transtext/libcppcmstranstext.la
@@ -33,17 +35,32 @@ if EN_FCGI_BACKEND
libcppcms_la_SOURCES += fcgi.cpp
endif

if EN_ENCR_SESSIONS
libcppcms_la_SOURCES += aes_encryptor.cpp
endif

if EN_SQLITE_SESSIONS
libcppcms_la_SOURCES += session_sqlite_storage.cpp
endif

nobase_pkginclude_HEADERS = global_config.h text_tool.h url.h cppcms_error.h \
manager.h worker_thread.h fcgi.h cache_interface.h archive.h \
base_cache.h thread_cache.h cgicc_connection.h scgi.h cgi_api.h \
process_cache.h shmem_allocator.h posix_mutex.h config.h cgi.h base_view.h \
util.h form.h application.h
util.h form.h application.h session_interface.h session_api.h session_cookies.h \
hmac_encryptor.h aes_encryptor.h encryptor.h md5.h base64.h session_backend_factory.h \
session_sid.h session_storage.h session_file_storage.h session_dual.h \
session_cache_backend.h session_sqlite_storage.h tcp_cache_protocol.h tcp_cache.h

if EN_TCP_CACHE
libcppcms_la_SOURCES += tcp_cache.cpp
bin_PROGRAMS = tcp_cache_server
tcp_cache_server_SOURCES = base_cache.cpp thread_cache.cpp tcp_cache_server.cpp
nobase_pkginclude_HEADERS += tcp_cache_protocol.h tcp_cache.h
libcppcms_la_SOURCES += tcp_cache.cpp tcp_messenger.cpp session_tcp_storage.cpp tcp_connector.cpp
bin_PROGRAMS = cppcms_tcp_scale
cppcms_tcp_scale_SOURCES = base_cache.cpp thread_cache.cpp tcp_cache_server.cpp session_file_storage.cpp
cppcms_tcp_scale_CXXFLAGS=-DNO_BUILDER_INTERFACE
if EN_SQLITE_SESSIONS
cppcms_tcp_scale_SOURCES += session_sqlite_storage.cpp
endif

endif



+ 45
- 28
aes_encryptor.cpp View File

@@ -7,6 +7,8 @@
#include "cppcms_error.h"
#include "aes_encryptor.h"

#include "base64.h"

using namespace std;

namespace cppcms {
@@ -26,59 +28,74 @@ class load {
cipher::cipher(string k) :
encryptor(k)
{
if(gcry_cipher_open(&hd,GCRY_CIPHER_AES,GCRY_CIPHER_MODE_CBC,0)<0){
throw cppcms_error("Create failed");
bool in=false,out=false;
in=gcry_cipher_open(&hd_in,GCRY_CIPHER_AES,GCRY_CIPHER_MODE_CBC,0)<0;
out=gcry_cipher_open(&hd_out,GCRY_CIPHER_AES,GCRY_CIPHER_MODE_CBC,0)<0;
if(in || out){
goto error_exit;
}

if( gcry_cipher_setkey(hd,&key.front(),16) < 0 ) {
gcry_cipher_close(hd);
throw cppcms_error("Set Key failed");
if( gcry_cipher_setkey(hd_in,&key.front(),16) < 0) {
goto error_exit;
}
if( gcry_cipher_setkey(hd_out,&key.front(),16) < 0)
goto error_exit;
char iv[16];
gcry_create_nonce(iv,sizeof(iv));
gcry_cipher_setiv(hd_out,iv,sizeof(iv));
return;
error_exit:
if(in) gcry_cipher_close(hd_in);
if(out) gcry_cipher_close(hd_out);
throw cppcms_error("AES cipher initialization failed");
}

cipher::~cipher()
cipher::~cipher()
{
gcry_cipher_close(hd);
gcry_cipher_close(hd_in);
gcry_cipher_close(hd_out);
}

string cipher::encrypt(string const &plain,time_t timeout)
{
size_t block_size=(plain.size() + 15) / 16 * 16;

vector<unsigned char> data(16+sizeof(info)+block_size,0);
info &header=*(info *)(&data.front()+16);
vector<unsigned char> data(sizeof(aes_hdr)+sizeof(info)+block_size,0);
copy(plain.begin(),plain.end(),data.begin() + sizeof(aes_hdr)+sizeof(info));
aes_hdr &aes_header=*(aes_hdr*)(&data.front());
info &header=*(info *)(&data.front()+sizeof(aes_hdr));
header.timeout=timeout;
header.size=plain.size();
salt(header.salt);
gcry_md_hash_buffer(GCRY_MD_MD5,&data.front()+16,&data.front(),block_size+sizeof(info));
gcry_cipher_encrypt(hd,&data.front(),data.size(),NULL,0);
gcry_cipher_reset(hd);
memset(&aes_header,0,16);

gcry_md_hash_buffer(GCRY_MD_MD5,&aes_header.md5,&header,block_size+sizeof(info));
gcry_cipher_encrypt(hd_out,&data.front(),data.size(),NULL,0);

return base64_enc(data);
}

bool cipher::decrypt(string const &cipher,string &plain,time_t *timeout)
{
if(cipher.size() % 16!=0) return false;
if(cipher.size()<16+sizeof(info)) return false;
vector<unsigned char> data;
base64_dec(cipher,data);
size_t norm_size=b64url::decoded_size(cipher.size());
if(norm_size<sizeof(info)+sizeof(aes_hdr) || norm_size % 16 !=0)
return false;

vector<char> data(cipher.begin(),cipher.end());
gcry_cipher_decrypt(hd,&data.front(),data.size(),NULL,0);
gcry_cipher_reset(hd);
gcry_cipher_decrypt(hd_in,&data.front(),data.size(),NULL,0);
gcry_cipher_reset(hd_in);
vector<char> md5(16,0);
gcry_md_hash_buffer(GCRY_MD_MD5,&md5.front(),&data.front()+16,data.size()-16);
if(!std::equal(md5.begin(),md5.end(),data.begin())) {
gcry_md_hash_buffer(GCRY_MD_MD5,&md5.front(),&data.front()+sizeof(aes_hdr),data.size()-sizeof(aes_hdr));
aes_hdr &aes_header = *(aes_hdr*)&data.front();
if(!std::equal(md5.begin(),md5.end(),aes_header.md5)) {
return false;
}
info &header=*(info *)(&data.front()+16);
time_t now;
time(&now);
if(now>header.timeout)
info &header=*(info *)(&data.front()+sizeof(aes_hdr));
if(time(NULL)>header.timeout)
return false;
if(timeout) *timeout=header.timeout;
plain.assign(data.begin()+16+sizeof(info),data.end());

plain.assign(data.begin()+sizeof(aes_hdr)+sizeof(info),data.end());
return true;
}



+ 10
- 5
aes_encryptor.h View File

@@ -10,17 +10,22 @@ namespace cppcms {
namespace aes {

class cipher : public encryptor {
gcry_cipher_hd_t hd;
gcry_cipher_hd_t hd_out;
gcry_cipher_hd_t hd_in;
struct aes_hdr {
char salt[16];
char md5[16];
};
public:
virtual std::string encrypt(std::string const &plain,time_t timeout) = 0;
virtual bool decrypt(std::string const &cipher,std::string &plain,time_t *timeout=NULL) = 0;
virtual std::string encrypt(std::string const &plain,time_t timeout);
virtual bool decrypt(std::string const &cipher,std::string &plain,time_t *timeout=NULL) ;
cipher(std::string key);
~cipher();
};

}
} // aes

}
} // cppcms


#endif


+ 1
- 0
application.cpp View File

@@ -9,6 +9,7 @@ application::application(worker_thread &w) :
env(worker.env),
cgi_conn(worker.cgi_conn),
cache(worker.cache),
session(worker.session),
cout(worker.cout),
on_start(worker.on_start),
on_end(worker.on_end)


+ 4
- 2
application.h View File

@@ -7,7 +7,7 @@ namespace cppcms {

struct application {

// Data
// Data
worker_thread &worker;
url_parser &url;
manager const &app;
@@ -16,6 +16,8 @@ struct application {
cgicc_connection *&cgi_conn;

cache_iface &cache;
session_interface &session;

ostream &cout;

boost::signal<void()> &on_start;
@@ -26,7 +28,7 @@ struct application {
virtual ~application();
// API

void set_header(HTTPHeader *h) { worker.set_header(h); }
void set_header(HTTPHeader *h) { worker.set_header(h); }
void add_header(string s) { worker.add_header(s); }
void set_cookie(cgicc::HTTPCookie const &c) { worker.set_cookie(c); }



+ 28
- 0
archive.h View File

@@ -23,6 +23,7 @@ public:
string &set() { ptr=0; return data; };
void set(char const *ptr,size_t len) { data.assign(ptr,len); };
string const &get() const { return data; };
string &get() { return data; }
template<typename T>
archive &operator<<(T const &val) {
size_t size=sizeof(T);
@@ -74,8 +75,35 @@ class serializable {
public:
virtual void load(archive &a) = 0;
virtual void save(archive &a) const = 0;
operator std::string() const
{
return str();
}
serializable const &operator=(std::string const &s)
{
str(s);
return *this;
}

void str(std::string const &s)
{
archive a(s);
load(a);
}
std::string str() const
{
archive a;
save(a);
string str;
str.swap(a.get());
return str;
}

virtual ~serializable() {};
};

}

#endif

+ 7
- 4
cache_interface.cpp View File

@@ -101,14 +101,16 @@ void cache_iface::rise(string const &t)
cms->caching_module->rise(t);
}

bool cache_iface::fetch_data(string const &key,serializable &data)
bool cache_iface::fetch_data(string const &key,serializable &data,bool notriggers)
{
if(!cms->caching_module) return false;
archive a;
set<string> new_trig;
if(cms->caching_module->fetch(key,a,new_trig)) {
data.load(a);
triggers.insert(new_trig.begin(),new_trig.end());
if(!notriggers){
triggers.insert(new_trig.begin(),new_trig.end());
}
return true;
}
return false;
@@ -125,14 +127,15 @@ void cache_iface::store_data(string const &key,serializable const &data,
cms->caching_module->store(key,triggers,deadtime(timeout),a);
}

bool cache_iface::fetch_frame(string const &key,string &result)
bool cache_iface::fetch_frame(string const &key,string &result,bool notriggers)
{
if(!cms->caching_module) return false;
archive a;
set<string> new_trig;
if(cms->caching_module->fetch(key,a,new_trig)) {
a>>result;
triggers.insert(new_trig.begin(),new_trig.end());
if(!notriggers)
triggers.insert(new_trig.begin(),new_trig.end());
return true;
}
return false;


+ 2
- 2
cache_interface.h View File

@@ -21,12 +21,12 @@ public:
void store_page(string const &key,int timeout=-1);
void rise(string const &trigger);
void add_trigger(string const &trigger);
bool fetch_frame(string const &key,string &result);
bool fetch_frame(string const &key,string &result,bool notriggers=false);
void store_frame(string const &key,
string const &frame,
set<string> const &triggers=set<string>(),
int timeout=-1);
bool fetch_data(string const &key,serializable &data);
bool fetch_data(string const &key,serializable &data,bool notriggers=false);
void store_data(string const &key,serializable const &data,
set<string> const &triggers=set<string>(),
int timeout=-1);


+ 55
- 2
config.txt View File

@@ -8,7 +8,7 @@ server.api = "fastcgi" # fastcgi -- preferred API

# Server work mode

server.mod="process" # process -- process runs single instance of worker thread. Very simple
server.mod="thread" # process -- process runs single instance of worker thread. Very simple
# suitable for server that manages process startup/shutdown
# This is only mod that supports CGI api
# thread -- thread pool execute several instances of worker. More dengerous
@@ -37,7 +37,7 @@ server.socket = "/tmp/hello-fastcgi.socket" # Default is "" -- use default s
# gzip.level = 1 # Default: zlib defailt, defines compression level
# gzip.buffer = 4096 # Default: zlib default, defines buffer size for zlib

# cache.backend = "fork" # Default "none" -- defines the cache backend to be used
# cache.backend = "threaded" # Default "none" -- defines the cache backend to be used
# "threaded" inprocess server suitable only for mod thread
# "fork" suitable for mod prefork only uses shared memory
# "tcp" distributed suitable for any other mod
@@ -62,3 +62,56 @@ locale.lang_default = "he" # default language (default first one)
locale.domain_list = { "app" "test" } # list of supported domains
locale.domain_default = "test" # default domain (default first one)


# General Session settings
# Default Expiration

session.expire = "browser" # "browser" "renew" "fixed"
session.timeout = 10 # 24*3600

# session.cookies_prefix = "cppcms_session"
# session.cookies_domain = ""
# session.cookies_path = "/"
# session.cookies_secure = 0

session.location = "server" # "none" "client" "server" "both" default none
#session.client_size_limit = 64 # The threshold for clinet/server storage defaul 2048


# Clinet side storage configuration
# session.cookies_encryptor = "aes" # "hmac" "aes" -- default "aes" unless unsupported
# hmac -- Preserves consistency
# aes ---Preserves consistency and secrecy
session.cookies_key = "126ba5b3f9c7d5a0b75f135c46cec946"
# Secret Private key: 32 hexadeximal digits


#session.backend = "files" # Server side storage backend: "cache", "files", "sqlite", "tcp"
# default -- "files"
# cache -- does not preserverd withing restarti!!!
# It is actually "empty" backend with enabled cache
# files -- save information in "files_dir" directory
# sqlite -- save information in sqlite DB
# tcp -- save information over distributed storage

#session.server_enable_cache = 0 # Add standard caching layer over storage backend
# May improve performance when working with "heavy sorages"
# like databases or files --- default disabled

#session.files_comp = "thread" # Serialization method for file backend
# "thread" -- safe for use with thread mod uses in memory mutexes
# "prefork" -- safe for use with prefork mod -- uses shared
# memory with mutexes
# "nfs" -- uses fcntl locking, safe for use over NFS when
# locking is supported and enabled
# allows sharing cache data over distributed network
# default -- according to "mod"

session.files_dir = "./sessions" # Location of session date for file backend
# default getenv("TMP")+/cppcms_sessions
session.files_gc_frequency = 3600 # Frequency of "garbage collection" --- cleaning sessions that
# had allready "time out". Default -1 --- disabled
session.sqlite_db = "./sessions/s.db" # The base name of slite DB for the sqlite storage
session.tcp_ips = { "127.0.0.1" }
session.tcp_ports = { 3000 }


+ 60
- 12
configure.in View File

@@ -16,27 +16,52 @@ AC_CONFIG_FILES([Makefile transtext/Makefile])
AC_ARG_ENABLE(forkcache,[AS_HELP_STRING([--disable-forkcache],[Disable shared memory cache])])
AC_ARG_ENABLE(fastcgi,[AS_HELP_STRING([--disable-fastcgi],[Disable fastcgi interface])])
AC_ARG_ENABLE(tcpcache,[AS_HELP_STRING([--disable-tcpcache],[Disable distributed cache system])])
AC_ARG_ENABLE(crypt,[AS_HELP_STRING([--disable-crypt],[Disable encrypted sessions backend])])
AC_ARG_ENABLE(sqlite,[AS_HELP_STRING([--disable-sqlite],[Disable sqlite sessions backend])])

CPPCMS_LIBS=""

AC_CHECK_LIB(pthread,pthread_sigmask,[],[echo "Pthreads library not found" ; exit -1])
AC_TRY_RUN([ #include <pthread.h>
int main()
{
pthread_rwlockattr_t attr;
pthread_rwlock_t lock;
return
( pthread_rwlockattr_init(&attr)==0
&& pthread_rwlockattr_setpshared(&attr,PTHREAD_PROCESS_SHARED)==0
&& pthread_rwlock_init(&lock,&attr)==0 )
? 0 : 1;
}
],
[AC_DEFINE([HAVE_PTHREADS_PSHARED],[],["Have Pshared"])
have_pthreads_pshared=yes
echo "Check: process shared mutex... ok"],[echo "Check: process shared mutex not supported"])
if test "x$enable_forkcache" != "xno" ; then
if test "x$have_pthreads_pshared" != "xyes" ; then
echo "======================================================================"
echo " Pthread process shared mutex/rwlock not supported "
echo " The fork cache backend is disabled "
echo " File based session storage process-shared backend is disabled"
echo "======================================================================"
enable_forkcache=no
fi
fi

if test "x$enable_forkcache" != "xno" ; then
case $host in
*cygwin*) echo "=============================================="
echo "Cygwin pthread process shared mutex is broken "
echo "Shared memory/fork cache will be disabled "
echo "==============================================" ;;
*)
AC_CHECK_LIB(mm,main,[
have_mm=yes
CPPCMS_LIBS="-lmm $CPPCMS_LIBS"
AC_DEFINE([EN_FORK_CACHE],[],["Enable fork cache"])
],
[ echo "======================================================================"
echo "OSSP mm library (libmm) not installed"
echo "============== The fork cache backend will be disabled ===============" ]) ;;
esac
echo " OSSP mm library (libmm) not installed"
echo " The fork cache backend is disabled "
echo "======================================================================" ])
fi

AM_CONDITIONAL(EN_FORK_CACHE,[test "x$have_mm" == "xyes" ])

if test "x$enable_fastcgi" != "xno" ; then
AC_CHECK_LIB(fcgi++,main,[
have_fcgi=yes
@@ -48,6 +73,32 @@ if test "x$enable_fastcgi" != "xno" ; then
echo "============== FastCGI API will be disabled =========================="
echo "You still have scgi and cgi API" ])
fi
AM_CONDITIONAL(EN_FCGI_BACKEND,[test "x$have_fcgi" == "xyes" ])

if test "x$enable_crypt" != "xno" ; then
AC_CHECK_LIB(gcrypt,main,[
have_gcrypt=yes
CPPCMS_LIBS="-lgcrypt $CPPCMS_LIBS"
AC_DEFINE([EN_ENCR_SESSIONS],[],["Enable encrypted sessions"])
],
[ echo "====================================================================="
echo "libgcrypt not found, Encrypted Sessions backend is disabled"
echo "=====================================================================" ])
fi
AM_CONDITIONAL(EN_ENCR_SESSIONS,[test "x$have_gcrypt" == "xyes" ])

if test "x$enable_sqlite" != "xno" ; then
AC_CHECK_LIB(sqlite3,sqlite3_open,[
have_sqlite3=yes
LIBS="-lsqlite3 $LIBS"
AC_DEFINE([EN_SQLITE_SESSIONS],[],["Enable sqlite sessions"])
],
[ echo "====================================================================="
echo "libsqlite3 not found, Sqlite sessions backend is disabled"
echo "=====================================================================" ])
fi
AM_CONDITIONAL(EN_SQLITE_SESSIONS,[test "x$have_sqlite3" == "xyes" ])


have_auto_type_detection=no

@@ -88,8 +139,6 @@ fi



AM_CONDITIONAL(EN_FORK_CACHE,[test "x$have_mm" == "xyes" ])
AM_CONDITIONAL(EN_FCGI_BACKEND,[test "x$have_fcgi" == "xyes" ])

AC_CHECK_HEADER(fastcgi/fcgiapp.h ,[AC_DEFINE([EN_FASTCGI_LONG_PATH],[],["Fastcgi headers in fastcgi dir"])],[])

@@ -139,7 +188,6 @@ AM_CONDITIONAL(EN_TCP_CACHE,[test "x$have_asio" == "xyes" ])

AC_CHECK_LIB(cgicc,main,[CPPCMS_LIBS="-lcgicc $CPPCMS_LIBS"],[echo "cgicc not found" ; exit -1])
AC_CHECK_LIB(dl,dlopen,[CPPCMS_LIBS="-ldl $CPPCMS_LIBS"],[])
AC_CHECK_LIB(pthread,pthread_sigmask,[],[echo "Pthreads library not found" ; exit -1])
AC_CHECK_LIB(boost_regex,main,[
CPPCMS_LIBS="-lboost_regex $CPPCMS_LIBS"
],


+ 21
- 7
cppcms.kdevelop View File

@@ -9,7 +9,8 @@
<ignoreparts/>
<projectdirectory>.</projectdirectory>
<absoluteprojectpath>false</absoluteprojectpath>
<description/>
<description></description>
<versioncontrol/>
</general>
<kdevautoproject>
<general>
@@ -50,16 +51,22 @@
<envvar value="1" name="WANT_AUTOCONF_2_5" />
<envvar value="1" name="WANT_AUTOMAKE_1_6" />
</envvars>
<abortonerror>true</abortonerror>
<runmultiplejobs>false</runmultiplejobs>
<numberofjobs>1</numberofjobs>
<dontact>false</dontact>
<makebin/>
<prio>0</prio>
</make>
</kdevautoproject>
<kdevdebugger>
<general>
<dbgshell>libtool</dbgshell>
<programargs>-c config.txt</programargs>
<gdbpath/>
<configGdbScript/>
<runShellScript/>
<runGdbScript/>
<gdbpath></gdbpath>
<configGdbScript></configGdbScript>
<runShellScript></runShellScript>
<runGdbScript></runGdbScript>
<breakonloadinglibs>true</breakonloadinglibs>
<separatetty>false</separatetty>
<floatingtoolbar>false</floatingtoolbar>
@@ -134,7 +141,7 @@
<qt>
<used>false</used>
<version>3</version>
<root/>
<root></root>
</qt>
<codecompletion>
<includeGlobalFunctions>true</includeGlobalFunctions>
@@ -149,7 +156,7 @@
<headerCompletionDelay>250</headerCompletionDelay>
</codecompletion>
<creategettersetter>
<prefixGet/>
<prefixGet></prefixGet>
<prefixSet>set</prefixSet>
<prefixVariable>m_,_</prefixVariable>
<parameterName>theValue</parameterName>
@@ -168,4 +175,11 @@
<hidenonprojectfiles>false</hidenonprojectfiles>
</tree>
</kdevfileview>
<kdevdocumentation>
<projectdoc>
<docsystem/>
<docurl/>
<usermanualurl/>
</projectdoc>
</kdevdocumentation>
</kdevelop>

+ 8
- 2
cppcms_error.h View File

@@ -9,9 +9,15 @@ namespace cppcms {


class cppcms_error : public std::runtime_error {
std::string strerror(int err)
{
char buf[256];
strerror_r(err,buf,sizeof(buf));
return buf;
}
public:
cppcms_error(int errno,std::string const &error):
std::runtime_error(error+":" + strerror(errno)) {};
cppcms_error(int err,std::string const &error):
std::runtime_error(error+":" + strerror(err)) {};
cppcms_error(std::string const &error) : std::runtime_error(error) {};
};



+ 9
- 0
cppcms_make_key View File

@@ -0,0 +1,9 @@
#!/bin/bash

SECRET=`od -An -t x4 -N16 /dev/random | awk '{ print $1$2$3$4}'`

echo
echo "# This is your PRIVATE KEY --- keep it in secret!"
echo "# Put this line into your configuration file"
echo
echo "session.cookies_key = \"$SECRET\""

+ 8
- 8
encryptor.cpp View File

@@ -8,20 +8,20 @@ using namespace std;

namespace cppcms {

encryptor::~encryptor()
encryptor::~encryptor()
{
}

encryptor::encryptor(string key_):
key(16,0)
{
if(key_.size()!=32) {
throw cppcms_error("Incorrect key length (32 expected)\n");
}
for(unsigned i=0;i<32;i+=2) {
char buf[3];
if(!isxdigit(key_[i]) || !isxdigit(key_[i+1])) {
if(!isxdigit(key_[i]) || !isxdigit(key_[i+1])) {
throw cppcms_error("Cipher should be encoded as hexadecimal 32 digits number");
}
buf[0]=key_[i];
@@ -33,7 +33,7 @@ encryptor::encryptor(string key_):
}
struct timeval tv;
gettimeofday(&tv,NULL);
seed=(unsigned)this+tv.tv_sec+tv.tv_usec+getpid();
seed=(unsigned)(intptr_t)this+tv.tv_sec+tv.tv_usec+getpid();
}

unsigned encryptor::rand(unsigned max)
@@ -45,8 +45,8 @@ string encryptor::base64_enc(vector<unsigned char> const &data)
{
size_t size=b64url::encoded_size(data.size());
vector<unsigned char> result(size,0);
b64url::encode(&data.front(),&data.front()+size,&result.front());
return string(data.begin(),data.end());
b64url::encode(&data.front(),&data.front()+data.size(),&result.front());
return string(result.begin(),result.end());
}

void encryptor::base64_dec(std::string const &in,std::vector<unsigned char> &data)
@@ -55,10 +55,10 @@ void encryptor::base64_dec(std::string const &in,std::vector<unsigned char> &dat
if(size<0) return;
data.resize(size);
unsigned char const *ptr=(unsigned char const *)in.data();
b64url::decode((unsigned char const *)ptr,ptr+size,&data.front());
b64url::decode((unsigned char const *)ptr,ptr+in.size(),&data.front());
}

void encryptor::salt(char *salt)
void encryptor::salt(char *salt)
{
info dummy;
for(unsigned i=0;i<sizeof(dummy.salt);i++)


+ 24
- 6
hello_world.cpp View File

@@ -19,12 +19,26 @@ public:

void my_hello_world::test()
{
if(cache.fetch_page("tst"))
return;
time_t tm;
time(&tm);
cout<<"<h1>"<<tm<<"</h1>";
cache.store_page("tst");
if(!session.is_set("time")) {
cout<<"No Time\n";
}
else {
time_t given=session.get<time_t>("time");
cout<<asctime(gmtime(&given))<<"<br/>\n";
if(session.is_set("msg")) {
cout<<session["msg"]<<"<br/>";
}
if(given % 3 == 0) {
cout<<"SET LONG MESSAGE";
session["msg"]="Looooooooooooooooooooooooooooooong msg";
}
else {
cout<<"UNSET LONG MESSAGE";
session.del("msg");
}
//session.clear();
}
session.set<time_t>("time",time(NULL));
}

void my_hello_world::std()
@@ -34,6 +48,7 @@ void my_hello_world::std()
if(env->getRequestMethod()=="POST") {
v.form.load(*cgi);
if(v.form.validate()) {
session["name"]=v.form.username.get();
v.username=v.form.username.get();
v.realname=v.form.name.get();
v.ok=v.form.ok.get();
@@ -43,6 +58,9 @@ void my_hello_world::std()
}

v.title="Cool";
if(session.is_set("name"))
v.title+=":"+session["name"];

v.msg=gettext("Hello World");

for(int i=0;i<15;i++)


+ 1
- 0
hmac_encryptor.cpp View File

@@ -35,6 +35,7 @@ string cipher::encrypt(string const &plain,time_t timeout)
header.timeout=timeout;
header.size=plain.size();
salt(header.salt);
copy(plain.begin(),plain.end(),data.begin()+16+sizeof(info));
hash(&data.front()+16,data.size()-16,&data.front());
return base64_enc(data);
}


+ 75
- 7
manager.cpp View File

@@ -19,9 +19,17 @@
#include "thread_cache.h"
#include "scgi.h"
#include "cgi.h"
#include "session_cookies.h"
#include "session_file_storage.h"
#include "session_cache_backend.h"
#include "session_dual.h"

#ifdef EN_SQLITE_SESSIONS
# include "session_sqlite_storage.h"
#endif

#ifdef EN_FORK_CACHE

# include "process_cache.h"
#endif

@@ -31,6 +39,7 @@

#ifdef EN_TCP_CACHE
# include "tcp_cache.h"
# include "session_tcp_storage.h"
#endif

namespace cppcms {
@@ -504,26 +513,80 @@ web_application *manager::get_mod()
throw cppcms_error("Unknown mod:" + mod);
}

namespace {
struct empty_backend {
shared_ptr<session_api> operator()(worker_thread &a)
{
return shared_ptr<session_api>(); // EMPTY
}
};
}

session_backend_factory manager::get_sessions()
{
string lock=config.sval("session.location","none");
if(lock=="none")
return empty_backend();
session_backend_factory clnt;
session_backend_factory srv;
if(lock=="client" || lock=="both") {
clnt=session_cookies::factory();
}
if(lock=="server" || lock=="both") {
string srv_backend=config.sval("session.backend","files");
if(srv_backend=="cache")
srv=session_cache_backend::factory();
else if(srv_backend=="files")
srv=session_file_storage::factory(config);
#ifdef EN_SQLITE_SESSIONS
else if(srv_backend=="sqlite")
srv=session_sqlite_storage::factory(config);
#endif
#ifdef EN_TCP_CACHE
else if(srv_backend=="tcp")
srv=session_tcp_storage::factory(config);
#endif
else
throw cppcms_error("Unknown backend:"+srv_backend);
}

if(lock=="server")
return srv;
if(lock=="client")
return clnt;
if(lock=="both") {
int limit=config.ival("session.client_size_limit",2048);
return session_dual::factory(clnt,srv,limit);
}
throw cppcms_error("Unknown location:"+lock);
}

void manager::execute()
{
if(!workers.get()) {
throw cppcms_error("No workers factory set up");
}
if(!cache.get()) {
set_cache(get_cache_factory());
}
if(sessions.empty()) {
set_sessions(get_sessions());
}
if(!api.get()) {
set_api(get_api());
}
if(!web_app.get()) {
set_mod(get_mod());
}
if(!gettext.get()){
set_gettext(get_gettext());
}
if(!workers.get()) {
throw cppcms_error("No workers factory set up");
if(!web_app.get()) {
set_mod(get_mod());
}

load_templates();
web_app->execute();
}

@@ -560,6 +623,11 @@ manager::~manager()
for_each(templates_list.begin(),templates_list.end(),::dlclose);
}

void manager::set_sessions(session_backend_factory s)
{
sessions=s;
}

void manager::set_worker(base_factory *w)
{
workers=auto_ptr<base_factory>(w);
@@ -585,7 +653,7 @@ transtext::trans_factory *manager::get_gettext()
transtext::trans_factory *tmp=NULL;
try{
tmp=new transtext::trans_factory();
tmp->load( config.sval ("locale.dir",""),
config.slist("locale.lang_list"),
config.sval ("locale.lang_default",""),


+ 5
- 3
manager.h View File

@@ -12,7 +12,7 @@
#include "cgi_api.h"
#include "posix_mutex.h"
#include "transtext.h"
#include "session_backend_factory.h"
#include <boost/shared_ptr.hpp>

namespace cppcms {
@@ -210,8 +210,9 @@ class manager : private boost::noncopyable {
cache_factory *get_cache_factory();
cgi_api *get_api();
web_application *get_mod();
session_backend_factory get_sessions();
transtext::trans_factory *get_gettext();
list<void *> templates_list;
list<void *> templates_list;
void load_templates();
public:
cppcms_config config;
@@ -219,7 +220,7 @@ public:
auto_ptr<cgi_api> api;
auto_ptr<base_factory> workers;
auto_ptr<web_application> web_app;
session_backend_factory sessions;
auto_ptr<transtext::trans_factory> gettext;

void set_worker(base_factory *w);
@@ -227,6 +228,7 @@ public:
void set_api(cgi_api *a);
void set_mod(web_application *m);
void set_gettext(transtext::trans_factory *);
void set_sessions(session_backend_factory);

manager();
manager(char const *file);


+ 22
- 0
session_api.h View File

@@ -0,0 +1,22 @@
#ifndef CPPCMS_SESSION_API_H
#define CPPCMS_SESSION_API_H
#include <boost/noncopyable.hpp>
#include <string>

namespace cppcms {

class worker_thread;
class session_interface;

class session_api : private boost::noncopyable {
public:
virtual void save(session_interface *,std::string const &data,time_t timeout, bool new_data) = 0;
virtual bool load(session_interface *,std::string &data,time_t &timeout) = 0;
virtual void clear(session_interface *) = 0;
virtual ~session_api(){};
};


} // cppcms

#endif

+ 18
- 0
session_backend_factory.h View File

@@ -0,0 +1,18 @@
#ifndef CPPCMS_SESSION_BACKEND_FACTORY_H
#define CPPCMS_SESSION_BACKEND_FACTORY_H

#include <string>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
namespace cppcms {

class session_api;
class worker_thread;

typedef boost::shared_ptr<session_api> session_backend_shared_ptr;
typedef boost::function<session_backend_shared_ptr(worker_thread &)> session_backend_factory;

} // namespace cppcms


#endif

+ 33
- 0
session_cache_backend.h View File

@@ -0,0 +1,33 @@
#ifndef CPPCMS_SESSIONS_CACHE_BACKEND_H
#define CPPCMS_SESSIONS_CACHE_BACKEND_H

#include <boost/shared_ptr.hpp>
#include "session_api.h"
#include "session_backend_factory.h"
#include "session_storage.h"
#include "session_sid.h"

namespace cppcms {
class worker_thread;
namespace session_cache_backend {

struct builder {
shared_ptr<session_api> operator()(worker_thread &w)
{
boost::shared_ptr<empty_session_server_storage> storage(new empty_session_server_storage());
return shared_ptr<session_api>(new session_sid(storage,true));
}
};

session_backend_factory factory()
{
return builder();
}


} // cache_backend

} //cppcms


#endif

+ 90
- 0
session_cookies.cpp View File

@@ -0,0 +1,90 @@
#include "config.h"

#include "session_interface.h"
#include "session_cookies.h"
#include "hmac_encryptor.h"
#include "worker_thread.h"
#include "manager.h"
#include "session_backend_factory.h"
#ifdef EN_ENCR_SESSIONS
#include "aes_encryptor.h"
#endif


using namespace std;

namespace cppcms {

namespace {
struct builder {
shared_ptr<session_api> operator()(worker_thread &w)
{
return shared_ptr<session_api>(new session_cookies(w));
}
};
}

session_backend_factory session_cookies::factory()
{
return builder();
}

session_cookies::session_cookies(worker_thread &w,auto_ptr<encryptor> enc) :
worker(w),
encr(enc)
{
}

session_cookies::session_cookies(worker_thread &w) :
worker(w)
{
#ifdef EN_ENCR_SESSIONS
string default_type="aes";
#else
string default_type="hmac";
#endif
string type=w.app.config.sval("session.cookies_encryptor",default_type);
string key=w.app.config.sval("session.cookies_key");
if(type=="hmac") {
encr.reset(new hmac::cipher(key));
return;
}
#ifdef EN_ENCR_SESSIONS
if(type=="aes") {
encr.reset(new aes::cipher(key));
return;
}
#endif
throw cppcms_error("Unknown encryptor "+type);
}

void session_cookies::save(session_interface *session,string const &data,time_t timeout,bool not_used)
{
string cdata=encr->encrypt(data,timeout);
session->set_session_cookie(cdata);
}

bool session_cookies::load(session_interface *session,string &data,time_t &timeout_out)
{
string cdata=session->get_session_cookie();
if(cdata.empty()) return false;
time_t timeout;
string tmp;
if(!encr->decrypt(cdata,tmp,&timeout))
return false;
time_t now;
time(&now);
if(timeout < now)
return false;
data.swap(tmp);
timeout_out=timeout;
return true;
}

void session_cookies::clear(session_interface *session)
{
session->clear_session_cookie();
}


};

+ 29
- 0
session_cookies.h View File

@@ -0,0 +1,29 @@
#ifndef CPPCMS_SESSION_COOKIES_H
#define CPPCMS_SESSION_COOKIES_H
#include "session_api.h"
#include <memory>
#include <string>
#include "session_backend_factory.h"

namespace cppcms {

class encryptor;
class worker_thread;
class session_interface;

class session_cookies : public session_api {
worker_thread &worker;
std::auto_ptr<encryptor> encr;
public:
static session_backend_factory factory();
session_cookies(worker_thread &w);
session_cookies(worker_thread &w,std::auto_ptr<encryptor>);
virtual void save(session_interface *,std::string const &data,time_t timeout,bool );
virtual bool load(session_interface *,std::string &data,time_t &timeout);
virtual void clear(session_interface *);
};


} // cppcms

#endif

+ 18
- 20
session_dbixx_storage.h View File

@@ -1,55 +1,53 @@
#ifndef CPPCMS_DBIXX_STORAGE_H
#define CPPCMS_DBIXX_STORAGE_H

#include "session_server_storage_with_cache.h"
#include "session_storage.h"
#include <dbixx/dbixx.h>

namespace cppcms {
class session_dbixx_storage : public session_server_storage_with_cache {
class session_dbixx_storage : public session_server_storage {
dbixx::session &sql;
public:
session_dbixx_storage(dbixx::session &sql_,cache_iface &cache_) :
session_server_storage_with_cache(cache_),
session_dbixx_storage(dbixx::session &sql_) :
sql(sql_)
{
}
protected:
virtual void impl_save(std::string const &sid,entry &e)
virtual void save(std::string const &sid,time_t timeout,std::string const &in)
{
dbixx::transaction tr(sql);
impl_remove(sid);
remove(sid);
std::tm t;
localtime_r(&e.timeout,&t);
localtime_r(&timeout,&t);
sql<<"INSERT INTO cppcms_sessions(sid,timeout,data) "
"VALUES (?,?,?)",sid,t,e.data;
"VALUES (?,?,?)",sid,t,in;
sql.exec();
tr.commit();
time_t now;
time(&now);
localtime_r(&now,&t);
sql<<"DELETE FROM cppcms_sessions WHERE timeout < ?",t;
sql.exec();
}

virtual bool impl_load(std::string const &sid,entry &e)
virtual bool load(std::string const &sid,time_t *timeout,std::string &out)
{
sql<<"SELECT timeout,data FROM cppcms_sessions WHERE sid=?",sid;
dbixx::row r;
if(sql.single(r)) {
std::tm t;
r>>t>>e.data;
time_t now;
time(&now);
e.timeout=mktime(&t);
if(e.timeout < now)
std::string data;
r>>t>>data;
time_t tmp=mktime(&t);
if(tmp<time(NULL))
return false;
if(*timeout) *timeout=tmp;
out.swap(data);
return true;
}
return false;
}
virtual void remove(std::string const &sid)
{
sql<<"DELETE FROM cppcms_sessions WHERE sid=?",sid;
time_t now=time(NULL);
std::tm tnow;
localtime_r(&tnow,&now);
sql<<"DELETE FROM cppcms_sessions WHERE sid=? OR timeout < ?",sid,tnow;
sql.exec();
}
};


+ 65
- 0
session_dual.cpp View File

@@ -0,0 +1,65 @@
#include "session_dual.h"
#include "session_interface.h"

using namespace std;

namespace cppcms {

void session_dual::save(session_interface *session,string const &data,time_t timeout,bool isnew)
{
if(data.size() > limit) {
server->save(session,data,timeout,isnew);
}
else {
if(session->get_session_cookie().size() == 32) {
server->clear(session);
}
client->save(session,data,timeout,isnew);
}
}

bool session_dual::load(session_interface *session,string &data,time_t &timeout)
{
if(session->get_session_cookie().size()==32) {
return server->load(session,data,timeout);
}
else {
return client->load(session,data,timeout);
}
}

void session_dual::clear(session_interface *session)
{
if(session->get_session_cookie().size()==32) {
server->clear(session);
}
else {
client->clear(session);
}
}

namespace {
struct builder {
session_backend_factory client,server;
size_t limit;
builder(session_backend_factory c,session_backend_factory s,size_t l) :
client(c),
server(s),
limit(l)
{
}
boost::shared_ptr<session_api> operator()(worker_thread &w)
{
boost::shared_ptr<session_api> c,s;
c=client(w);
s=server(w);
return boost::shared_ptr<session_api>(new session_dual(c,s,limit));
}
};
}

session_backend_factory session_dual::factory(session_backend_factory c,session_backend_factory s,size_t l)
{
return builder(c,s,l);
}
} // cppcms

+ 31
- 0
session_dual.h View File

@@ -0,0 +1,31 @@
#ifndef CPPCMS_SESSION_DUAL_H
#define CPPCMS_SESSION_DUAL_H

#include "session_api.h"
#include "session_backend_factory.h"
#include <boost/shared_ptr.hpp>

namespace cppcms {

class session_dual : public session_api {
boost::shared_ptr<session_api> client;
boost::shared_ptr<session_api> server;
size_t limit;
public:
static session_backend_factory factory(session_backend_factory c,session_backend_factory s,size_t l);
session_dual(boost::shared_ptr<session_api> c,boost::shared_ptr<session_api> s,size_t l) :
client(c),
server(s),
limit(l)
{
}
virtual void save(session_interface *,std::string const &data,time_t timeout,bool new_session);
virtual bool load(session_interface *,std::string &data,time_t &timeout);
virtual void clear(session_interface *);

};

}


#endif

+ 512
- 0
session_file_storage.cpp View File

@@ -0,0 +1,512 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <boost/crc.hpp>
#include <boost/bind.hpp>
#include "global_config.h"

#include "session_file_storage.h"
#include "cppcms_error.h"
#include "session_sid.h"

#include "config.h"

using namespace std;

namespace cppcms {

namespace storage {

#define LOCK_SIZE 256
class io_error : public std::runtime_error {
public:
io_error() : std::runtime_error("IO"){};
};

void io::close(int fid) const
{
::close(fid);
}

int io::lock_id(std::string const &sid) const
{
int id;
char buf[3] = {0};
buf[0]=sid.at(0);
buf[1]=sid.at(1);
sscanf(buf,"%x",&id);
return id;
}

string io::mkname(std::string const &sid) const
{
return dir+"/"+sid;
}
void io::write(std::string const &sid,time_t timestamp,void const *buf,size_t len) const
{
string name=mkname(sid);
int fid=::open(name.c_str(),O_CREAT | O_TRUNC | O_WRONLY ,0666);
if(fid<0) {
throw cppcms_error(errno,"storage::local_id");
}
::write(fid,&timestamp,sizeof(time_t));
::write(fid,buf,len);
boost::crc_32_type crc_calc;
crc_calc.process_bytes(buf,len);
uint32_t crc=crc_calc.checksum();
::write(fid,&crc,4);
close(fid);
}

bool io::read(std::string const &sid,time_t &timestamp,vector<unsigned char> *out) const
{
int fid=-1;
string name=mkname(sid);
try {
fid=::open(name.c_str(),O_RDONLY);
if(fid<0) {
return false;
}
time_t tmp;
if(::read(fid,&tmp,sizeof(time_t))!=sizeof(time_t) || tmp < time(NULL))
throw io_error();
timestamp=tmp;
if(!out) {
close(fid);
return true;
}
int size=lseek(fid,0,SEEK_END);
if(size==-1 || size <(int)sizeof(time_t)+4)
throw io_error();
size-=sizeof(time_t)+4;
if(lseek(fid,sizeof(time_t),SEEK_SET) < 0)
throw io_error();
out->resize(size,0);
if(::read(fid,&out->front(),size)!=size)
throw io_error();
boost::crc_32_type crc_calc;
crc_calc.process_bytes(&out->front(),size);
uint32_t crc_ch,crc=crc_calc.checksum();
if(::read(fid,&crc_ch,4)!=4 || crc_ch != crc)
throw io_error();
close(fid);
return true;
}
catch(io_error const &e){
if(fid>=0)
close(fid);
::unlink(name.c_str());
return false;
}
catch(...) {
if(fid>=0) close(fid);
::unlink(name.c_str());
throw;
}
}

void io::unlink(std::string const &sid) const
{
string name=mkname(sid);
::unlink(name.c_str());
}

local_io::local_io(std::string dir,pthread_rwlock_t *l):
io(dir),
locks(l)
{
}

void local_io::wrlock(std::string const &sid) const
{
pthread_rwlock_wrlock(&locks[lock_id(sid)]);
}
void local_io::rdlock(std::string const &sid) const
{
pthread_rwlock_rdlock(&locks[lock_id(sid)]);
}
void local_io::unlock(std::string const &sid) const
{
pthread_rwlock_unlock(&locks[lock_id(sid)]);
}

void nfs_io::close(int fid)
{
::fsync(fid);
::close(fid);
}

nfs_io::nfs_io(std::string dir) : io(dir)
{
string lockf=dir+"/"+"nfs.lock";
fid=::open(lockf.c_str(),O_CREAT | O_RDWR,0666);
if(fid<0) {
throw cppcms_error(errno,"storage::nfs_io::open");
}
::lseek(fid,LOCK_SIZE,SEEK_SET);
::write(fid,"",1);
}

nfs_io::~nfs_io()
{
::close(fid);
}

namespace {

bool flock(int fid,int how,int pos)
{
struct flock lock;
memset(&lock,0,sizeof(lock));
lock.l_type=how;
lock.l_whence=SEEK_SET;
lock.l_start=pos;
lock.l_len=1;
int res;
while((res=fcntl(fid,F_SETLKW,&lock)!=0) && errno==EINTR)
;
if(res) return false;
return true;
}

} // anon namespace


void nfs_io::wrlock(std::string const &sid) const
{
if(!flock(fid,F_WRLCK,lock_id(sid)))
throw cppcms_error(errno,"storage::nfs_io::fcntl::WRITE LOCK");
}
void nfs_io::rdlock(std::string const &sid) const
{
if(!flock(fid,F_RDLCK,lock_id(sid)))
throw cppcms_error(errno,"storage::nfs_io::fcntl::READ LOCK");
}
void nfs_io::unlock(std::string const &sid) const
{
flock(fid,F_UNLCK,lock_id(sid));
}


pthread_rwlock_t *thread_io::create_locks()
{
pthread_rwlock_t *array = new pthread_rwlock_t [LOCK_SIZE];
for(int i=0;i<LOCK_SIZE;i++) {
if(pthread_rwlock_init(array+i,NULL)) {
int err=errno;
i--;
for(;i>=0;i--) {
pthread_rwlock_destroy(array+i);
}
delete [] array;
throw cppcms_error(err,"storage:pthread_rwlock_init");
}
}
return array;
}

thread_io::thread_io(std::string dir) :
local_io(dir,create_locks())
{
}

thread_io::~thread_io()
{
for(int i=0;i<LOCK_SIZE;i++) {
pthread_rwlock_destroy(locks+i);
}
delete [] locks;
}

#ifdef HAVE_PTHREADS_PSHARED

pthread_rwlock_t *shmem_io::create_locks()
{
int size=sizeof(pthread_rwlock_t)*LOCK_SIZE;
void *ptr=mmap(0,size,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS, 0,0);
if(ptr==MAP_FAILED) {
throw cppcms_error(errno,"storage:mmap");
}
pthread_rwlock_t *array =(pthread_rwlock_t*)ptr;
for(int i=0;i<LOCK_SIZE;i++) {
pthread_rwlockattr_t attr;
if( pthread_rwlockattr_init(&attr) != 0
|| pthread_rwlockattr_setpshared(&attr,PTHREAD_PROCESS_SHARED) != 0
|| pthread_rwlock_init(array+i,&attr) !=0)
{
int err=errno;
i--;
for(;i>=0;i--) {
pthread_rwlock_destroy(array+i);
}
munmap(ptr,size);
throw cppcms_error(err,"storage:pthread_rwlock_init");
}
pthread_rwlockattr_destroy(&attr);
}
creator_pid=getpid();
return array;
}

shmem_io::shmem_io(std::string dir) :
local_io(dir,create_locks())
{
}

shmem_io::~shmem_io()
{
if(creator_pid==getpid()) {
for(int i=0;i<LOCK_SIZE;i++) {
pthread_rwlock_destroy(locks+i);
}
}
munmap(locks,sizeof(*locks)*LOCK_SIZE);
}

#endif // HAVE_PTHREADS_PSHARED

} // namespace storeage

void session_file_storage::save(string const &sid,time_t timeout,string const &in)
{
try {
io->wrlock(sid);
io->write(sid,timeout,in.data(),in.size());
io->unlock(sid);
}
catch(...) {
io->unlock(sid);
throw;
}
}

bool session_file_storage::load(string const &sid,time_t *timeout,string &out)
{
try {
io->rdlock(sid);
vector<unsigned char> data;
time_t tmp;
bool res=io->read(sid,tmp,&data);
io->unlock(sid);
if(timeout) *timeout=tmp;
out.assign(data.begin(),data.end());
return res;
}
catch(...) {
io->unlock(sid);
throw;
}
}

void session_file_storage::remove(string const &sid)
{
try {
io->wrlock(sid);
io->unlink(sid);
io->unlock(sid);
}
catch(...) {
io->unlock(sid);
throw;
}
}

void session_file_storage::gc(boost::shared_ptr<storage::io> io)
{
DIR *d=NULL;
try{
string dir=io->get_dir();
d=opendir(dir.c_str());
struct dirent entry,*entry_p;
while(readdir_r(d,&entry,&entry_p)==0 && entry_p!=NULL) {
int i;
for(i=0;i<32;i++) {
if(!isxdigit(entry.d_name[i]))
break;
}
if(i!=32 || entry.d_name[i]!=0)
continue;
string sid=entry.d_name;
try{
io->rdlock(sid);
time_t tmp;
io->read(sid,tmp,NULL);
// if it timeouted -- will be removed
io->unlock(sid);
}
catch(...) {
io->unlock(sid);
throw;
}
}
closedir(d);
}
catch(...) {
if(d) closedir(d);
throw;
}
}


#ifndef NO_BUILDER_INTERFACE

namespace {

static pthread_mutex_t gc_mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t gc_cond=PTHREAD_COND_INITIALIZER;
static int gc_exit=-1;
static int gc_period=-1;
static int starter_pid=-1;

void *thread_func(void *param);

struct builder_impl : private boost::noncopyable {
public:
boost::shared_ptr<storage::io> io;
bool has_thread;
pthread_t pid;
int cache;

string def_dir()
{
char const *d=getenv("TEMP");
if(!d) d=getenv("TMP");
if(!d) d="/tmp";
string dir=string(d)+"/cppcms_sessions";
if(::mkdir(dir.c_str(),0777)!=0 && errno!=EEXIST)
throw cppcms_error(errno,"session::file_storage::mkdir");
return dir;
}
builder_impl(cppcms_config const config)
{
gc_exit=-1;
cache=config.ival("session.server_enable_cache",0);
string dir=config.sval("session.files_dir","");
if(dir=="") {
dir=def_dir();
}
string mod = config.sval("server.mod","");
string default_type;
if(mod=="thread")
default_type = "thread";
#ifdef HAVE_PTHREADS_PSHARED
else if(mod=="prefork")
default_type = "prefork";
#endif
else
default_type = "nfs";
string type=config.sval("session.files_comp",default_type);
if(type=="thread")
io.reset(new storage::thread_io(dir));
#ifdef HAVE_PTHREADS_PSHARED
else if(type=="prefork")
io.reset(new storage::shmem_io(dir));
#endif
else if(type=="nfs")
io.reset(new storage::nfs_io(dir));
else
throw cppcms_error("Unknown option for session.files_comp `"+type+"'");
// Clean first time
session_file_storage::gc(io);

gc_period=config.ival("session.files_gc_frequency",-1);

if(gc_period>0) {
has_thread=true;
gc_exit=0;
starter_pid=getpid();
pthread_create(&pid,NULL,thread_func,this);
}
else
has_thread=false;

}
~builder_impl()
{
if(has_thread) {
pthread_mutex_lock(&gc_mutex);
gc_exit=1;
pthread_cond_signal(&gc_cond);
pthread_mutex_unlock(&gc_mutex);
pthread_join(pid,NULL);
}
}

boost::shared_ptr<session_api> operator()(worker_thread &w)
{
boost::shared_ptr<session_server_storage> ss(new session_file_storage(io));
return boost::shared_ptr<session_sid>(new session_sid(ss,cache));
}
};

struct builder {
boost::shared_ptr<builder_impl> the_builder;

builder(boost::shared_ptr<builder_impl> b) :
the_builder(b)
{
}
boost::shared_ptr<session_api> operator()(worker_thread &w)
{
return (*the_builder)(w);
}

};

void *thread_func(void *param)
{
builder_impl *blder=(builder_impl*)param;
int exit=0;
while(!exit) {
pthread_mutex_lock(&gc_mutex);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec+=gc_period;
pthread_cond_timedwait(&gc_cond,&gc_mutex,&ts);
if(starter_pid!=getpid() || gc_exit) exit=1;
pthread_mutex_unlock(&gc_mutex);
if(!exit) {
try {
session_file_storage::gc(blder->io);
}
catch(std::exception const &e) {
fprintf(stderr,"%s\n",e.what());
return NULL;
}
catch(...)
{
return NULL;
}
}
}
return NULL;
}

} // anon namespace


session_backend_factory session_file_storage::factory(cppcms_config const &conf)
{
return builder(boost::shared_ptr<builder_impl>(new builder_impl(conf)));
}

#else // !NO_BUILDER_INTERFACE

session_backend_factory session_file_storage::factory(cppcms_config const &conf)
{
throw runtime_error("session_file_storage::factory should not be used");
}

#endif



} // namespace cppcms

+ 97
- 0
session_file_storage.h View File

@@ -0,0 +1,97 @@
#ifndef SESSION_FILE_STORAGE_H
#define SESSION_FILE_STORAGE_H

#include <string>
#include <vector>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <pthread.h>
#include "session_storage.h"
#include "session_backend_factory.h"
#include "config.h"


namespace cppcms {
class cppcms_config;

namespace storage {

class io : private boost::noncopyable {
protected:
std::string dir;
int lock_id(std::string const &sid) const;
std::string mkname(std::string const &sid) const;
virtual void close(int fid) const;
public:
std::string const &get_dir() const { return dir; }
io(std::string d) : dir(d) {}
virtual void wrlock(std::string const &sid) const = 0;
virtual void rdlock(std::string const &sid) const = 0;
virtual void unlock(std::string const &sid) const = 0;
virtual void write(std::string const &sid,time_t timestamp,void const *buf,size_t len) const;
virtual bool read(std::string const &sid,time_t &timestamp,std::vector<unsigned char> *out) const;
virtual void unlink(std::string const &sid) const;
virtual ~io(){};
};

class local_io : public io {
protected:
pthread_rwlock_t *locks;
public:
local_io(std::string dir,pthread_rwlock_t *l);
virtual void wrlock(std::string const &sid) const;
virtual void rdlock(std::string const &sid) const;
virtual void unlock(std::string const &sid) const;
};

class nfs_io : public io {
int fid;
protected:
virtual void close(int fid);
public:
nfs_io(std::string dir);
virtual void wrlock(std::string const &sid) const;
virtual void rdlock(std::string const &sid) const;
virtual void unlock(std::string const &sid) const;
~nfs_io();
};

class thread_io : public local_io
{
pthread_rwlock_t *create_locks();
public:
thread_io(std::string dir);
~thread_io();
};

#ifdef HAVE_PTHREADS_PSHARED

class shmem_io : public local_io
{
int creator_pid;
pthread_rwlock_t *create_locks();
public:
shmem_io(std::string dir);
~shmem_io();
};

#endif

} // storage

class session_file_storage : public session_server_storage {
boost::shared_ptr<storage::io> io;
public:
static void gc(boost::shared_ptr<storage::io>);
static session_backend_factory factory(cppcms_config const &);
session_file_storage(boost::shared_ptr<storage::io> io_) : io(io_) {}
virtual void save(std::string const &sid,time_t timeout,std::string const &in);
virtual bool load(std::string const &sid,time_t *timeout,std::string &out);
virtual void remove(std::string const &sid) ;
virtual ~session_file_storage(){};
};

} // cppcms


#endif

+ 195
- 0
session_interface.cpp View File

@@ -0,0 +1,195 @@
#include "worker_thread.h"
#include "session_interface.h"
#include "session_api.h"
#include "manager.h"
#include "archive.h"

namespace cppcms {

session_interface::session_interface(worker_thread &w) :
worker(w)
{
timeout_val_def=w.app.config.ival("session.timeout",24*3600);
string s_how=w.app.config.sval("session.expire","browser");
if(s_how=="fixed") {
how_def=fixed;
}
else if(s_how=="renew") {
how_def=renew;
}
else if(s_how=="browser") {
how_def=browser;
}
else {
throw cppcms_error("Unsupported `session.expire' type `"+s_how+"'");
}


storage=w.app.sessions(w);
}

worker_thread &session_interface::get_worker()
{
return worker;
}

void session_interface::set_api(boost::shared_ptr<session_api> s)
{
storage=s;
}
bool session_interface::load()
{
data.clear();
data_copy.clear();
timeout_val=timeout_val_def;
how=how_def;
archive ar;
if(!storage.get() || !storage->load(this,ar.set(),timeout_in)) {
return false;
}
int i,num;
ar>>num;
for(i=0;i<num;i++) {
string key,val;
ar>>key>>val;
data[key].swap(val);
}
data_copy=data;
return true;
}

int session_interface::cookie_age()
{
if(how==browser)
return 0;
if(how==renew || ( how==fixed && new_session ))
return timeout_val;
return timeout_in - time(NULL);
}

time_t session_interface::session_age()
{
if(how==browser || how==renew || (how==fixed && new_session))
return timeout_val + time(NULL);
return timeout_in;
}

void session_interface::save()
{
check();
new_session = data_copy.empty() && !data.empty();
if(data.empty()) {
if(get_session_cookie()!="")
storage->clear(this);
return;
}

time_t now = time(NULL);

if(data==data_copy) {
if(how==fixed) {
return;
}
if(how==renew || how==browser) {
int64_t delta=now + timeout_val - timeout_in;
if(delta < timeout_val * 0.1) {// Less then 10% -- no renew need
return;
}
}
}
int num=data.size();
archive ar;
ar<<num;
for(map<string,string>::iterator p=data.begin(),e=data.end();p!=e;++p) {
ar<<p->first<<p->second;
}
temp_cookie.clear();
storage->save(this,ar.get(),session_age(),new_session);
set_session_cookie(cookie_age(),temp_cookie);
temp_cookie.clear();
}

void session_interface::on_start()
{
load();
}

void session_interface::on_end()
{
if(storage.get()!=NULL)
save();
}

void session_interface::check()
{
if(storage.get()==NULL)
throw cppcms_error("Session storage backend is not loaded\n");
}

string &session_interface::operator[](string const &key)
{
return data[key];
}

void session_interface::del(string const &key)
{
data.erase(key);
}

bool session_interface::is_set(string const &key)
{
return data.find(key)!=data.end();
}

void session_interface::clear()
{
data.clear();
}

void session_interface::clear_session_cookie()
{
if(get_session_cookie()!="")
set_session_cookie(-1,"");
}
void session_interface::set_session_cookie(int64_t age,string const &data)
{
if(data.empty())
age=0;
cgicc::HTTPCookie
cookie( worker.app.config.sval("session.cookies_prefix","cppcms_session"), // name
(age >= 0 ? data : ""), // value
"", // comment
worker.app.config.sval("session.cookies_domain",""), // domain
( age < 0 ? 0 : age ),
worker.app.config.sval("session.cookies_path","/"),
worker.app.config.ival("session.cookies_secure",0));
worker.set_cookie(cookie);
}
void session_interface::set_session_cookie(string const &data)
{
temp_cookie=data;
}

string session_interface::get_session_cookie()
{
string name=worker.app.config.sval("session.cookies_prefix","cppcms_session");
vector<cgicc::HTTPCookie> const &cookies=worker.env->getCookieList();
for(unsigned i=0;i<cookies.size();i++) {
if(cookies[i].getName()==name)
return cookies[i].getValue();
}
return string("");
}
void session_interface::get(std::string const &key,serializable &s)
{
s=(*this)[key];
}
void session_interface::set(std::string const &key,serializable const &s)
{
(*this)[key]=s;
}


};


+ 82
- 0
session_interface.h View File

@@ -0,0 +1,82 @@
#ifndef CPPCMS_SESSION_INTERFACE_H
#define CPPCMS_SESSION_INTERFACE_H

#include <boost/noncopyable.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <map>

namespace cppcms {

class session_api;
class worker_thread;
class serializable;

class session_interface : private boost::noncopyable {
std::map<std::string,std::string> data,data_copy;
worker_thread &worker;

// Cached defaults
int timeout_val_def;
int how_def;

// User Values
int timeout_val;
int how;

// Information from session data
time_t timeout_in;
bool new_session;

int cookie_age();
time_t session_age();

void check();
bool load();
void save();

std::string temp_cookie;

boost::shared_ptr<session_api> storage;
void set_session_cookie(int64_t age,std::string const &data);

public:
session_interface(worker_thread &w);
bool is_set(std::string const &key);
void del(std::string const &key);
std::string &operator[](std::string const &);
template<typename T>
T get(std::string const &key) {
return boost::lexical_cast<T>((*this)[key]);
}
template<typename T>
void set(std::string const &key,T const &val) {
(*this)[key]=boost::lexical_cast<std::string>(val);
}
void get(std::string const &key,serializable &);
void set(std::string const &key,serializable const &);


void clear();

enum { fixed, renew, browser };
void set_age(int t) { timeout_val=t;}
void set_expiration(int h) { how=h; };

// Special interface
void set_session_cookie(std::string const &data);
void clear_session_cookie();
std::string get_session_cookie();
void set_api(boost::shared_ptr<session_api>);

void on_start();
void on_end();
worker_thread &get_worker();
};

} // cppcms


#endif

+ 0
- 44
session_server_storage_with_cache.cpp View File

@@ -1,44 +0,0 @@
#include "session_server_storage_with_cache.h"
#include "cache_interface.h"

namespace cppcms {

void session_server_storage_with_cache::save(std::string const &sid,time_t timeout,std::string const &in)
{
string cache_key="cppcms_sid_"+sid;
cache.rise(cache_key);
entry e;
e.timeout=timeout;
e.data=in;
impl_save(sid,e);
time_t now;
time(&now);
cache.store_data(cache_key,e,set<string>(),timeout-now);
}
bool session_server_storage_with_cache::load(std::string const &sid,time_t *timeout,std::string &out)
{
string cache_key="cppcms_sid_"+sid;
entry e;
if(cache.fetch_data(cache_key,e)) {
if(timeout) *timeout=e.timeout;
out.swap(e.data);
return true;
}
time_t now;
time(&now);
if(impl_load(sid,e)) {
if(timeout) *timeout=e.timeout;
cache.store_data(cache_key,e,set<string>(),int(timeout-now));
out.swap(e.data);
return true;
}
return false;
}
void session_server_storage_with_cache::remove(std::string const &sid)
{
string cache_key="cppcms_sid_"+sid;
cache.rise(cache_key);
impl_remove(sid);
}

} // cppcms

+ 0
- 36
session_server_storage_with_cache.h View File

@@ -1,36 +0,0 @@
#ifndef CPPCMS_SESSION_SERVER_STORAGE_WITH_CACHE_H
#define CPPCMS_SESSION_SERVER_STORAGE_WITH_CACHE_H

#include "session_storage.h"
#include "archive.h"

namespace cppcms {

class cache_iface;

class session_server_storage_with_cache : public session_server_storage {
cache_iface &cache;
protected:
struct entry : public serializable {
time_t timeout;
std::string data;
void load(archive &a) { a >> timeout >> data; }
void save(archive &a) const { a <<timeout << data; }
};
virtual void impl_save(std::string const &sid,entry &e) = 0;
virtual bool impl_load(std::string const &sid,entry &e) = 0;
virtual void impl_remove(std::string const &sid) = 0;
public:
session_server_storage_with_cache(cache_iface &cache_) :
cache(cache_)
{
}
virtual void save(std::string const &sid,time_t timeout,std::string const &in);
virtual bool load(std::string const &sid,time_t *timeout,std::string &out);
virtual void remove(std::string const &sid);
};

} // cppcms

#endif

+ 152
- 0
session_sid.cpp View File

@@ -0,0 +1,152 @@
#include "session_sid.h"
#include "md5.h"
#include "session_storage.h"
#include "session_interface.h"
#include <fstream>
#include "cppcms_error.h"
#include "archive.h"
#include "worker_thread.h"

using namespace std;

namespace cppcms {
namespace details {

sid_generator::sid_generator()
{
hashed.session_counter=0;
ifstream urandom("/dev/urandom");
if(!urandom.good() || urandom.get(hashed.uid,16).fail()) {
throw cppcms_error("Failed to read /dev/urandom");
}
}

std::string sid_generator::operator()()
{
hashed.session_counter++;
gettimeofday(&hashed.tv,NULL);
md5_byte_t md5[16];
char res[33];
md5_state_t st;
md5_init(&st);
md5_append(&st,(md5_byte_t*)&hashed,sizeof(hashed));
md5_finish(&st,md5);
for(int i=0;i<16;i++) {
snprintf(res+i*2,3,"%02x",md5[i]);
}
return std::string(res);
}


} // namespace details

namespace {
struct cached_data : public serializable {
time_t timeout;
std::string data;
virtual void load(archive &a)
{
a>>timeout>>data;
}
virtual void save(archive &a) const
{
a<<timeout<<data;
}

};
}


bool session_sid::valid_sid(std::string const &id)
{
if(id.size()!=32)
return false;
for(int i=0;i<32;i++) {
char c=id[i];
bool is_low_x_digit=('0'<=c && c<='9') || ('a'<=c && c<='f');
if(!is_low_x_digit)
return false;
}
return true;
}

string session_sid::key(std::string sid)
{
return "cppcms_session_"+sid;
}

void session_sid::save(session_interface *session,std::string const &data,time_t timeout,bool new_data)
{
string id;
if(!new_data) {
id=session->get_session_cookie();
if(!valid_sid(id)) {
id=sid(); // if id not valid create new one
}
}
else {
id=sid();
}

if(cache){
session->get_worker().cache.rise(key(id));
}
storage->save(id,timeout,data);
session->set_session_cookie(id); // Renew cookie or set new one
if(cache) {
cached_data cdata;
cdata.timeout=timeout;
cdata.data=data;
session->get_worker().cache.store_data(
key(id),
cdata,
set<string>(),
timeout - time(NULL));
}
}

bool session_sid::load(session_interface *session,std::string &data,time_t &timeout)
{
string id=session->get_session_cookie();
if(!valid_sid(id))
return false;
if(cache){
cached_data cdata;
if(session->get_worker().cache.fetch_data(key(id),cdata,false)) {
data.swap(cdata.data);
timeout=cdata.timeout;
return true;
}
}
if(!storage->load(id,&timeout,data)) {
return false;
}
if(!cache)
return true;
cached_data cdata;
cdata.timeout=timeout;
cdata.data=data;
session->get_worker().cache.store_data(
key(id),
cdata,
set<string>(),
timeout-time(NULL)
);
return true;
}

void session_sid::clear(session_interface *session)
{
string id=session->get_session_cookie();
if(valid_sid(id)) {
storage->remove(id);
if(cache)
session->get_worker().cache.rise(key(id));
}
}


} // namespace cppcms

+ 46
- 0
session_sid.h View File

@@ -0,0 +1,46 @@
#ifndef CPPCMS_SESSION_SID_H
#define CPPCMS_SESSION_SID_H

#include <sys/time.h>
#include <boost/shared_ptr.hpp>
#include "session_api.h"

namespace cppcms {
class session_server_storage;
class session_interface;
namespace details {
class sid_generator : public boost::noncopyable {
struct for_hash {
char uid[16];
uint64_t session_counter;
struct timeval tv;
} hashed;
public:
sid_generator();
std::string operator()();
};
}

class session_sid : public session_api {
details::sid_generator sid;
boost::shared_ptr<session_server_storage> storage;
bool cache;
std::string key(std::string sid);
public:
bool valid_sid(std::string const &str);
session_sid(boost::shared_ptr<session_server_storage> s,bool c=false) :
storage(s),
cache(c)
{
}
virtual void save(session_interface *,std::string const &data,time_t timeout,bool);
virtual bool load(session_interface *,std::string &data,time_t &timeout);
virtual void clear(session_interface *);

};
}


#endif

+ 315
- 0
session_sqlite_storage.cpp View File

@@ -0,0 +1,315 @@
#include "session_sid.h"
#include "global_config.h"
#include "session_sqlite_storage.h"
#include "cppcms_error.h"
#include "posix_mutex.h"
#include <sqlite3.h>
#include <pthread.h>
#include <poll.h>
#include <boost/lexical_cast.hpp>

namespace cppcms {
namespace storage {

class sqlite {
sqlite3 *db;
pthread_mutex_t mutexes;
int write_ops;
time_t last_commit;
int deferred_commit_count;
int deferred_commit_time;
pthread_mutex_t mutex;

public:
void exec(char const *s)
{
char *err=NULL;
int res;
while((res=sqlite3_exec(db,s,NULL,NULL,&err))!=0) {
if(res==SQLITE_BUSY) {
sqlite3_free(err);
poll(NULL,0,5);
continue;
}
string err_msg=err;
sqlite3_free(err);
throw cppcms_error(err_msg);
}
}
sqlite (string db_name,
bool sync_,
int deferred_commit_count_,
int deferred_commit_time_) :
db(0),
deferred_commit_count(deferred_commit_count_),
deferred_commit_time (deferred_commit_time_)
{
pthread_mutex_init(&mutex,NULL);
try {
if(sqlite3_open(db_name.c_str(),&db)) {
string error=sqlite3_errmsg(db);
throw cppcms_error(error);
}
exec( "CREATE TABLE IF NOT EXISTS sessions ( "
" sid varchar(32) primary key not null, "
" data varchar(1) not null, "
" timeout integer not null "
")");
exec( "CREATE INDEX IF NOT EXISTS sessions_timeout "
" ON sessions(timeout)");
if(!sync_) {
exec( "PRAGMA synchronous=OFF");
}
write_ops=0;
last_commit=time(NULL);
exec("begin");
}
catch(...) {
if(db) sqlite3_close(db);
pthread_mutex_destroy(&mutex);
throw;
}
}
~sqlite()
{
try {
exec("commit");
}
catch(...) {
}
if(db) sqlite3_close(db);
if(deferred_commit_count > 0)
pthread_mutex_destroy(&mutex);
}
void check_commits()
{
long long int now=time(NULL);
if( write_ops >= deferred_commit_count
|| (deferred_commit_time > 0 && last_commit + deferred_commit_time < now))
{
char stat[128];
snprintf(stat,sizeof(stat),"DELETE FROM sessions WHERE timeout < %lld",now);
exec(stat);
exec("commit");
exec("begin");
last_commit=time(NULL);
write_ops=0;
}
}
void exec(char const *s,string const &sid,string const &data,int64_t timeout)
{
sqlite3_stmt *st;
if(sqlite3_prepare(db,s,-1,&st,NULL)!=0) {
throw cppcms_error(string("sqlite prepared statement:")+sqlite3_errmsg(db));
}
sqlite3_bind_text(st,1, sid.c_str(), sid.size(),SQLITE_STATIC);
sqlite3_bind_blob(st,2,data.c_str(),data.size(),SQLITE_STATIC);
sqlite3_bind_int64(st,3,timeout);
int res;
while((res=sqlite3_step(st))==SQLITE_BUSY){
poll(NULL,0,5);
}
if(res==SQLITE_DONE) {
sqlite3_finalize(st);
}
else {
throw cppcms_error(string("Insert error:")+sqlite3_errmsg(db));
}
}
bool select(string const &sid,time_t &timeout,string &data)
{
sqlite3_stmt *st;
char const *q="SELECT data,timeout FROM sessions WHERE sid=?";
if(sqlite3_prepare(db,q,-1,&st,NULL)!=0) {
throw cppcms_error(string("sqlite prepared statement:")+sqlite3_errmsg(db));
}
int res;
sqlite3_bind_text(st,1, sid.c_str(), sid.size(),SQLITE_STATIC);
while((res=sqlite3_step(st))==SQLITE_BUSY){
poll(NULL,0,5);
}
if(res==SQLITE_DONE) {
sqlite3_finalize(st);
return false;
}
else if(res==SQLITE_ROW) {
int64_t to=sqlite3_column_int64(st,1);
if(to < time(NULL)) {
sqlite3_finalize(st);
del(sid);
write_ops++;
return false;
}
size_t length=sqlite3_column_bytes(st,0);
data.assign((const char *)sqlite3_column_blob(st,0),length);
timeout=to;
sqlite3_finalize(st);
return true;
}
else {
throw cppcms_error(string("Insert error:")+sqlite3_errmsg(db));
}
}
void save(string const &sid,time_t timeout,string const &data)
{
mutex_lock lock(mutex);
exec("INSERT OR REPLACE INTO sessions VALUES(?,?,?)",sid,data,timeout);
write_ops++;
check_commits();

}
void del(string const &sid)
{
char q[128];
snprintf(q,sizeof(q),"DELETE FROM sessions WHERE sid='%s'",sid.c_str());
exec(q);
}
void remove(string const &sid)
{
mutex_lock lock(mutex);
del(sid);
write_ops++;
check_commits();
}
bool load(std::string const &sid,time_t *timeout,std::string &out)
{
mutex_lock lock(mutex);
time_t tout;
if(!select(sid,tout,out)) {
check_commits();
return false;
}
if(timeout) *timeout=tout;
check_commits();
return true;
}
};


sqlite &sqlite_N::db(string const &sid)
{
char buf[3]={ sid.at(10) , sid.at(15) , 0 };
int v;
sscanf(buf,"%x",&v);
v = v%size;
return *dbs.at(v);
}
sqlite_N::sqlite_N(string db,int n,bool sync,int def_comm,int def_to) :
size(n)
{
dbs.resize(n);
int i;
for(i=0;i<n;i++) {
string fname=db+"_"+boost::lexical_cast<string>(i);
dbs[i]=boost::shared_ptr<sqlite>(new sqlite(fname,sync,def_comm,def_to));
}
}

bool sqlite_N::load(std::string const &sid,time_t *timeout,std::string &out)
{
return db(sid).load(sid,timeout,out);
}
void sqlite_N::remove(string const &sid)
{
db(sid).remove(sid);
}
void sqlite_N::save(string const &sid,time_t timeout,string const &data)
{
db(sid).save(sid,timeout,data);
}

} // storage

#ifndef NO_BUILDER_INTERFACE

namespace {

// The database is created at startup
struct builder_thread {
boost::shared_ptr<storage::sqlite_N> db;
builder_thread(string dir,int n,bool sync,int dc,int dt)
: db(new storage::sqlite_N(dir,n,sync,dc,dt))
{
}
boost::shared_ptr<session_api> operator()(worker_thread &w)
{
boost::shared_ptr<session_server_storage> storage(new session_sqlite_storage(db));
return boost::shared_ptr<session_api>(new session_sid(storage));
}
};

// The database created *AFTER* forks + no deferred commits
struct builder_proc {
string dir;
bool sync;
int size;
builder_proc(string d,int n,bool s) : dir(d) , sync(s) , size(n)
{
}
boost::shared_ptr<session_api> operator()(worker_thread &w)
{
boost::shared_ptr<storage::sqlite_N> db(new storage::sqlite_N(dir,size,sync,0,0));
boost::shared_ptr<session_server_storage> storage(new session_sqlite_storage(db));
return boost::shared_ptr<session_api>(new session_sid(storage));
}

};

}

session_backend_factory session_sqlite_storage::factory(cppcms_config const &config)
{
string db=config.sval("session.sqlite_db");
int db_count=config.ival("session.sqlite_db_num",4);
if(db_count>8)
db_count=8;
if(db_count<0)
db_count=0;
db_count=1<<db_count;
string def="fork";
if(config.sval("server.mod","")=="thread")
def="thread";
string mod=config.sval("session.sqlite_mod",def);
if(mod=="fork") {
bool sync=config.ival("session.sqlite_sync",0);
return builder_proc(db,db_count,sync);
}
else if(mod=="thread") {
bool sync=config.ival("session.sqlite_sync",1);
int dc=config.ival("session.sqlite_commits",1000);
int dt=config.ival("session.sqlite_commit_timeout",5);
return builder_thread(db,db_count,sync,dc,dt);
}
else {
throw cppcms_error("Unknown sqlite mode:"+mod);
}
}

#else // NO_BUILDER_INTERFACE
session_backend_factory session_sqlite_storage::factory(cppcms_config const &config)
{
throw runtime_error("session_sqlite_storage::factory should bot be used");
}
#endif


session_sqlite_storage::session_sqlite_storage(boost::shared_ptr<storage::sqlite_N> db_):
db(db_)
{
}
void session_sqlite_storage::save(std::string const &sid,time_t timeout,std::string const &in)
{
db->save(sid,timeout,in);
}
bool session_sqlite_storage::load(std::string const &sid,time_t *timeout,std::string &out)
{
return db->load(sid,timeout,out);
}
void session_sqlite_storage::remove(std::string const &sid)
{
return db->remove(sid);
}

} // cppcms

+ 44
- 0
session_sqlite_storage.h View File

@@ -0,0 +1,44 @@
#ifndef SESSION_SQLITE_STORAGE_H
#define SESSION_SQLITE_STORAGE_H

#include <string>
#include <vector>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include "session_storage.h"
#include "session_backend_factory.h"

namespace cppcms {
class cppcms_config;

namespace storage {

struct sqlite;

class sqlite_N {
vector<boost::shared_ptr<sqlite> > dbs;
unsigned size;
sqlite &db(std::string const &sid);
public:
sqlite_N(string db,int n,bool sync,int def_commits,int def_timeout);
void save(string const &sid,time_t timeout,string const &data);
bool load(std::string const &sid,time_t *timeout,std::string &out);
void remove(string const &sid);
};

} // storage

class session_sqlite_storage : public session_server_storage {
boost::shared_ptr<storage::sqlite_N> db;
public:
static session_backend_factory factory(cppcms_config const &);
session_sqlite_storage(boost::shared_ptr<storage::sqlite_N> );
virtual void save(std::string const &sid,time_t timeout,std::string const &in);
virtual bool load(std::string const &sid,time_t *timeout,std::string &out);
virtual void remove(std::string const &sid) ;
};

} // cppcms


#endif

+ 20
- 1
session_storage.h View File

@@ -2,6 +2,7 @@
#define SESSION_STORAGE_H

#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
#include <string>

namespace cppcms {
@@ -11,7 +12,25 @@ public:
virtual void save(std::string const &sid,time_t timeout,std::string const &in) = 0;
virtual bool load(std::string const &sid,time_t *timeout,std::string &out) = 0;
virtual void remove(std::string const &sid) = 0;
virtual ~session_server_storage(){};
virtual ~session_server_storage()
{
}
};


class empty_session_server_storage :public session_server_storage
{
public:
void save(std::string const &sid,time_t timeout,std::string const &in)
{
}
bool load(std::string const &sid,time_t *timeout,std::string &out)
{
return false;
}
void remove(std::string const &sid)
{
}
};

} // cppcms


+ 82
- 0
session_tcp_storage.cpp View File

@@ -0,0 +1,82 @@
#include "session_tcp_storage.h"
#include "session_sid.h"
#include "manager.h"

namespace cppcms {

unsigned session_tcp_storage::hash(string const &key)
{
if(conns==1) return 0;
char buf[5] = { key.at(0) , key.at(1), key.at(2), key.at(3) , 0};
unsigned val=0;
sscanf(buf,"%x",&val);
return val % conns;;
}

void session_tcp_storage::save(std::string const &sid,time_t timeout,std::string const &in)
{
time_t now=time(NULL);
if(now > timeout) {
return ;
}
tcp_operation_header h={0};
h.opcode=opcodes::session_save;
h.size=in.size() + 32;
h.operations.session_save.timeout=timeout - now;
string data=sid;
data.append(in.begin(),in.end());
get(sid).transmit(h,data);
}

bool session_tcp_storage::load(std::string const &sid,time_t *timeout,std::string &out)
{
tcp_operation_header h={0};
h.opcode=opcodes::session_load;
h.size=sid.size();
string data=sid;
get(sid).transmit(h,data);
if(h.opcode==opcodes::session_load_data) {
if(timeout) *timeout=time(NULL) + h.operations.session_data.timeout;
out.swap(data);
return true;
}
else {
return false;
}
}

void session_tcp_storage::remove(std::string const &sid)
{
tcp_operation_header h={0};
h.opcode=opcodes::session_remove;
h.size=sid.size();
string data=sid;
get(sid).transmit(h,data);
}

namespace {

struct builder {
vector<string> ips;
vector<int> ports;
builder(vector<string> const &ips_,vector<int> const &ports_) :
ips(ips_), ports(ports_)
{
}
boost::shared_ptr<session_api> operator()(worker_thread &w)
{
boost::shared_ptr<session_server_storage> storage(new session_tcp_storage(ips,ports));
return boost::shared_ptr<session_api>(new session_sid(storage));
}
} ;

} // anon


session_backend_factory session_tcp_storage::factory(cppcms_config const &conf)
{
return builder(conf.slist("session.tcp_ips"),conf.ilist("session.tcp_ports"));
}

} // cppcms

+ 28
- 0
session_tcp_storage.h View File

@@ -0,0 +1,28 @@
#ifndef CPPCMS_SESSION_TCP_STORAGE_H
#define CPPCMS_SESSION_TCP_STORAGE_H

#include "session_storage.h"
#include "tcp_connector.h"
#include "tcp_messenger.h"
#include "session_backend_factory.h"

namespace cppcms {

class cppcms_config;

class session_tcp_storage : public session_server_storage , public tcp_connector {
protected:
virtual unsigned hash(std::string const &key);
public:
session_tcp_storage(std::vector<std::string> const &ips,std::vector<int> const &ports) :
tcp_connector(ips,ports)
{
}
static session_backend_factory factory(cppcms_config const &);
virtual void save(std::string const &sid,time_t timeout,std::string const &in);
virtual bool load(std::string const &sid,time_t *timeout,std::string &out);
virtual void remove(std::string const &sid);
};

} // cppcms
#endif

+ 4
- 121
tcp_cache.cpp View File

@@ -1,119 +1,11 @@
#include "config.h"
#ifdef __CYGWIN__
// Cygwin ASIO works only with win32 sockets
#define _WIN32_WINNT 1
#define __USE_W32_SOCKETS 1
#endif

#ifdef USE_BOOST_ASIO
#include <boost/asio.hpp>
namespace aio = boost::asio;
using boost::system::error_code;
using boost::system::system_error;
#else
#include <asio.hpp>
namespace aio = asio;
using asio::error_code;
using asio::system_error;
#endif

#include "tcp_messenger.h"
#include "tcp_cache.h"
#include "tcp_cache_protocol.h"
#include "archive.h"

using aio::ip::tcp;

namespace cppcms {

class messenger : boost::noncopyable {
aio::io_service srv_;
tcp::socket socket_;
string ip_;
int port_;
public:
void connect(string ip,int port) {
ip_=ip;
port_=port;
error_code e;
socket_.connect(tcp::endpoint(aio::ip::address::from_string(ip),port),e);
if(e) throw cppcms_error("connect:"+e.message());
tcp::no_delay nd(true);
socket_.set_option(nd);
}
messenger(string ip,int port) :
socket_(srv_)
{
connect(ip,port);
}
messenger() : socket_(srv_) { };

void transmit(tcp_operation_header &h,string &data)
{
bool done=false;
int times=0;
do {
try {
aio::write(socket_,aio::buffer(&h,sizeof(h)));
if(h.size>0) {
aio::write(socket_,aio::buffer(data,h.size));
}
aio::read(socket_,aio::buffer(&h,sizeof(h)));
if(h.size>0) {
vector<char> d(h.size);
aio::read(socket_,aio::buffer(d,h.size));
data.assign(d.begin(),d.begin()+h.size);
}
done=true;
}
catch(system_error const &e) {
if(times) {
throw cppcms_error(string("tcp_cache:")+e.what());
}
socket_.close();
error_code er;
socket_.connect(
tcp::endpoint(
aio::ip::address::from_string(ip_),port_),er);
if(er) throw cppcms_error("reconnect:"+er.message());
times++;
}
}while(!done);
}
};

tcp_cache::tcp_cache(vector<string> const& ip,vector<int> const &port)
{
if(ip.size()<1 || port.size()!=ip.size()) {
throw cppcms_error("Incorrect parameters for tcp cache");
}
conns=ip.size();
tcp=new messenger[conns];
try {
for(int i=0;i<conns;i++) {
tcp[i].connect(ip[i],port[i]);
}
}
catch(...) {
delete [] tcp;
tcp=NULL;
throw;
}
}

tcp_cache::~tcp_cache()
{
delete [] tcp;
}

void tcp_cache::broadcast(tcp_operation_header &h,string &data)
{
int i;
for(i=0;i<conns;i++) {
tcp_operation_header ht=h;
string dt=data;
tcp[i].transmit(ht,data);
}
// Nothing
}

void tcp_cache::rise(string const &trigger)
@@ -135,6 +27,7 @@ void tcp_cache::clear()
broadcast(h,empty);
}


bool tcp_cache::fetch_page(string const &key,string &output,bool gzip)
{
string data=key;
@@ -213,18 +106,8 @@ void tcp_cache::store(string const &key,set<string> const &triggers,time_t timeo
get(key).transmit(h,data);
}

messenger &tcp_cache::get(string const &key)
{
if(conns==1) return tcp[0];
unsigned val=0,i;
for(i=0;i<key.size();i++) {
val+=251*key[i]+103 % 307;
}
return tcp[val % conns];
}


}
} // cppcms


#ifdef TCP_CACHE_UNIT_TEST


+ 9
- 7
tcp_cache.h View File

@@ -2,6 +2,8 @@
#define TCP_CHACHE_H
#include "base_cache.h"
#include "cache_interface.h"
#include "session_storage.h"
#include "tcp_connector.h"
#include <string>

namespace cppcms {
@@ -11,13 +13,12 @@ using namespace std;
class messenger;
struct tcp_operation_header;

class tcp_cache : public base_cache {
messenger *tcp;
int conns;
messenger &get(string const &key);
void broadcast(tcp_operation_header &h,string &data);
class tcp_cache : public base_cache, public tcp_connector {
public:
tcp_cache(vector<string> const &ip_list,vector<int> const &port_list);
tcp_cache(vector<string> const &ip_list,vector<int> const &port_list) :
tcp_connector(ip_list,port_list)
{
}

virtual bool fetch_page(string const &key,string &output,bool gzip);
virtual bool fetch(string const &key,archive &a,set<string> &tags);
@@ -26,6 +27,7 @@ public:
virtual void stats(unsigned &keys,unsigned &triggers);
virtual void store(string const &key,set<string> const &triggers,time_t timeout,archive const &a);
virtual ~tcp_cache();

};

class tcp_cache_factory : public cache_factory {
@@ -38,6 +40,6 @@ public:
virtual void del(base_cache *p) const { delete p; };
};

}
} // cppcms

#endif

+ 8
- 1
tcp_cache_protocol.h View File

@@ -9,7 +9,8 @@ namespace cppcms {
namespace opcodes {
#endif
enum { fetch_page, fetch, rise, clear, store ,stats,
error, done, page_data, data, no_data, out_stats };
error, done, page_data, data, no_data, out_stats,
session_save, session_load, session_load_data, session_remove};
#ifdef __cplusplus
}
#endif
@@ -46,6 +47,12 @@ struct tcp_operation_header {
uint32_t keys;
uint32_t triggers;
} out_stats;
struct {
uint32_t timeout;
} session_save;
struct {
uint32_t timeout;
} session_data;
} operations;
};



+ 316
- 15
tcp_cache_server.cpp View File

@@ -15,6 +15,7 @@ namespace aio = asio;
using asio::error_code;
#endif
#include "tcp_cache_protocol.h"
#include "session_storage.h"
#include "archive.h"
#include "thread_cache.h"
#include <boost/bind.hpp>
@@ -22,6 +23,14 @@ using asio::error_code;
#include <boost/enable_shared_from_this.hpp>
#include <ctime>
#include <cstdlib>
#include <pthread.h>
#include <signal.h>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "session_file_storage.h"
#ifdef EN_SQLITE_SESSIONS
#include "session_sqlite_storage.h"
#endif

using namespace std;
using namespace cppcms;
@@ -39,7 +48,11 @@ class session : public boost::enable_shared_from_this<session> {
public:
tcp::socket socket_;
base_cache &cache;
session(aio::io_service &srv,base_cache &c) : socket_(srv), cache(c) {}
session_server_storage &sessions;
session(aio::io_service &srv,base_cache &c,session_server_storage &s) :
socket_(srv), cache(c),sessions(s)
{
}
void run()
{
aio::async_read(socket_,aio::buffer(&hin,sizeof(hin)),
@@ -157,6 +170,46 @@ public:
cache.store(key,triggers,timeout,a);
hout.opcode=opcodes::done;
}
void save()
{
if(hin.size <= 32)
{
hout.opcode=opcodes::error;
return;
}
time_t timeout=hin.operations.session_save.timeout + time(NULL);
string sid(data_in.begin(),data_in.begin()+32);
string value(data_in.begin()+32,data_in.end());
sessions.save(sid,timeout,value);
hout.opcode=opcodes::done;
}
void load()
{
if(hin.size!=32) {
hout.opcode=opcodes::error;
return;
}
time_t timeout;
int toffset;
string sid(data_in.begin(),data_in.end());
if(!sessions.load(sid,&timeout,data_out) && (toffset=(timeout-time(NULL))) < 0) {
hout.opcode=opcodes::no_data;
return;
}
hout.opcode=opcodes::session_load_data;
hout.size=data_out.size();
hout.operations.session_data.timeout=toffset;
}
void remove()
{
if(hin.size!=32) {
hout.opcode=opcodes::error;
return;
}
string sid(data_in.begin(),data_in.end());
sessions.remove(sid);
}
void on_data_in(error_code const &e)
{
if(e) return;
@@ -168,6 +221,9 @@ public:
case opcodes::clear: clear(); break;
case opcodes::store: store(); break;
case opcodes::stats: stats(); break;
case opcodes::session_save: save(); break;
case opcodes::session_load: load(); break;
case opcodes::session_remove: remove(); break;
default:
hout.opcode=opcodes::error;
}
@@ -197,6 +253,7 @@ public:
class tcp_cache_server {
tcp::acceptor acceptor_;
base_cache &cache;
session_server_storage &sessions;
void on_accept(error_code const &e,shared_ptr<session> s)
{
if(!e) {
@@ -208,48 +265,292 @@ class tcp_cache_server {
}
void start_accept()
{
shared_ptr<session> s(new session(acceptor_.io_service(),cache));
shared_ptr<session> s(new session(acceptor_.io_service(),cache,sessions));
acceptor_.async_accept(s->socket_,boost::bind(&tcp_cache_server::on_accept,this,aio::placeholders::error,s));
}
public:
tcp_cache_server( aio::io_service &io,
string ip,
int port,
base_cache &c) :
base_cache &c,
session_server_storage &s) :
acceptor_(io,
tcp::endpoint(aio::ip::address::from_string(ip),
port)),
cache(c)
cache(c),
sessions(s)
{
start_accept();
}
};

struct params {
bool en_cache;
enum { none , files , sqlite3 } en_sessions;
string session_backend;
string session_file;
string session_dir;
int items_limit;
int gc_frequency;
int files_no;
int port;
string ip;
int threads;

int main(int argc,char **argv)
void help()
{
cerr<< "Usage cppcms_tcp_scale [parameter]\n"
" --bind IP ipv4/ipv6 IPto bind (default 0.0.0.0)\n"
" --port N port to bind -- MANDATORY\n"
" --threads N number of threads, default 1\n"
" --cache Enable cache module\n"
" --limit N maximal Number of items to store\n"
" mandatory if cache enabled\n"
" --session-files Enable files bases session backend\n"
" --dir Directory where files stored\n"
" mandatory if session-files enabled\n"
" --gc N gc frequencty seconds (default 600)\n"
" it is enabled if threads > 1\n"
#ifdef EN_SQLITE_SESSIONS
" --session-sqlite3 Enable sqlite session backend\n"
" --file Sqlite3 DB file. Mandatory for sqlite\n"
" session backend\n"
" --dbfiles Number of DB files, default 0\n"
" 0->1 file, 1-> 2 files, 2 -> 4 files, etc\n"
#endif
"\n"
" At least one of --session-files,"
#ifdef EN_SQLITE_SESSIONS
" --session-sqlite3,"
#endif
" --cache\n"
" should be defined\n"
"\n";
}
params(int argc,char **argv) :
en_cache(false),
en_sessions(none),
items_limit(-1),
gc_frequency(-1),
files_no(0),
port(-1),
ip("0.0.0.0"),
threads(1)
{
argv++;
while(*argv) {
string param=*argv;
char *next= *(argv+1);
if(param=="--bind" && next) {
ip=next;
argv++;
}
else if(param=="--port" && next) {
port=atoi(next);
argv++;
}
else if(param=="--threads" && next) {
threads=atoi(next);
argv++;
}
else if(param=="--gc" && next) {
gc_frequency=atoi(next);
argv++;
}
else if(param=="--limit" && next) {
items_limit=atoi(next);
argv++;
}
else if(param=="--session-files") {
en_sessions=files;
}
else if(param=="--dir" && next) {
session_dir=next;
argv++;
}
#ifdef EN_SQLITE_SESSIONS
else if(param=="--file" && next) {
session_file=next;
argv++;
}
else if(param=="--dbfiles" && next) {
files_no=atoi(next);
argv++;
}
else if(param=="--session-sqlite3") {
en_sessions=sqlite3;
}
#endif
else if(param=="--cache") {
en_cache=true;
}
else {
help();
throw runtime_error("Incorrect parameter:"+param);
}
argv++;
}
if(!en_cache && !en_sessions) {
help();
throw runtime_error("Neither cache nor sessions mods are defined");
}
if(en_sessions == files && session_dir.empty()) {
help();
throw runtime_error("parameter --dir undefined");
}
if(en_sessions == sqlite3 && session_file.empty()) {
help();
throw runtime_error("patameter --file undefined");
}
if(files_no == -1) files_no=1;
if(port==-1) {
help();
throw runtime_error("parameter --port undefined");
}
if(en_cache && items_limit == -1) {
help();
throw runtime_error("parameter --limit undefined");
}
if(gc_frequency != -1) {
if(threads == 1) {
throw runtime_error("You have to use more then one thread to enable gc");
}
}
if(threads > 1 && gc_frequency==-1) {
gc_frequency = 600;
}
}
};

class garbage_collector
{
if(argc!=4) {
cerr<<"Usage: tcp_cache_server ip port entries-limit"<<endl;
return 1;
aio::deadline_timer timer;
boost::shared_ptr<storage::io> io;
int seconds;
void submit()
{
timer.expires_from_now(boost::posix_time::seconds(seconds));
timer.async_wait(boost::bind(&garbage_collector::gc,this,_1));
}
try
void gc(aio::error_code const &e)
{
aio::io_service io;
thread_cache cache(atoi(argv[3]));
tcp_cache_server srv_cache(io,argv[1],atoi(argv[2]),cache);
for(;;) {
session_file_storage::gc(io);
submit();
}
public:
garbage_collector(aio::io_service &srv,int sec,boost::shared_ptr<storage::io> io_) :
timer(srv),
seconds(sec),
io(io_)
{
submit();
}
};


void *thread_function(void *ptr)
{
aio::io_service &io=*(aio::io_service *)(ptr);
bool stop=false;
try{
while(!stop) {
try {
io.run();
break;
stop=true;
}
catch(cppcms_error const &e) {
// Not much to do...
// Object will be destroyed automatically
// Because it does not resubmit itself
cerr<<"CppCMS Error:"<<e.what()<<endl;
fprintf(stderr,"CppCMS Error %s\n",e.what());
}
}
}
catch(exception const &e)
{
fprintf(stderr,"Fatal:%s",e.what());
}
catch(...){
fprintf(stderr,"Unknown exception");
}
io.stop();
return NULL;
}


int main(int argc,char **argv)
{
try
{
params par(argc,argv);

aio::io_service io;

auto_ptr<base_cache> cache;
auto_ptr<session_server_storage> storage;
auto_ptr <garbage_collector> gc;

if(par.en_cache)
cache.reset(new thread_cache(par.items_limit));
else
cache.reset(new base_cache());

if(par.en_sessions==params::files) {
boost::shared_ptr<storage::io> storage_io(new storage::thread_io(par.session_dir));
storage.reset(new session_file_storage(storage_io));
if(par.threads > 1 && par.gc_frequency > 0) {
gc.reset(new garbage_collector(io,par.gc_frequency,storage_io));
}
}
#ifdef EN_SQLITE_SESSIONS
else if(par.en_sessions==params::sqlite3) {
boost::shared_ptr<storage::sqlite_N>
sql(new storage::sqlite_N(par.session_file,1<<par.files_no,true,1000,5));
storage.reset(new session_sqlite_storage(sql));
}
#endif
else {
storage.reset(new empty_session_server_storage());
}

tcp_cache_server srv_cache(io,par.ip,par.port,*cache,*storage);

sigset_t new_mask;
sigfillset(&new_mask);
sigset_t old_mask;
pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask);

vector<pthread_t> threads;
threads.resize(par.threads);

int i;
for(i=0;i<par.threads;i++){
if(pthread_create(&threads[i],NULL,thread_function,&io)!=0) {
perror("pthread_create failed:");
io.stop();
for(i=i-1;i>=0;i--) {
pthread_join(threads[i],NULL);
}
}
}

// Wait for signlas for exit
sigset_t wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGINT);
sigaddset(&wait_mask, SIGQUIT);
sigaddset(&wait_mask, SIGTERM);
pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
int sig = 0;
sigwait(&wait_mask, &sig);

io.stop();

for(i=0;i<par.threads;i++) {
pthread_join(threads[i],NULL);
}
}
catch(std::exception const &e) {
cerr<<"Error:"<<e.what()<<endl;
return 1;


+ 56
- 0
tcp_connector.cpp View File

@@ -0,0 +1,56 @@
#include "tcp_connector.h"
#include "tcp_messenger.h"

namespace cppcms {

tcp_connector::tcp_connector(vector<string> const& ip,vector<int> const &port)
{
if(ip.size()<1 || port.size()!=ip.size()) {
throw cppcms_error("Incorrect parameters for tcp cache");
}
conns=ip.size();
tcp=new messenger[conns];
try {
for(int i=0;i<conns;i++) {
tcp[i].connect(ip[i],port[i]);
}
}
catch(...) {
delete [] tcp;
tcp=NULL;
throw;
}
}

tcp_connector::~tcp_connector()
{
delete [] tcp;
}

void tcp_connector::broadcast(tcp_operation_header &h,string &data)
{
int i;
for(i=0;i<conns;i++) {
tcp_operation_header ht=h;
string dt=data;
tcp[i].transmit(ht,data);
}
}

unsigned tcp_connector::hash(string const &key)
{
if(conns==1) return 0;
unsigned val=0,i;
for(i=0;i<key.size();i++) {
val+=251*key[i]+103 % 307;
}
return val % conns;;
}

messenger &tcp_connector::get(string const &key)
{
return tcp[hash(key)];
}

} // cppcms


+ 31
- 0
tcp_connector.h View File

@@ -0,0 +1,31 @@
#ifndef CPPCMS_TCP_CONNECTOR_H
#define CPPCMS_TCP_CONNECTOR_H

#include <string>
#include <vector>
#include <boost/noncopyable.hpp>

namespace cppcms {

class messenger;
struct tcp_operation_header;

class tcp_connector : private boost::noncopyable
{
protected:
messenger *tcp;
int conns;
virtual unsigned hash(std::string const &key);
messenger &get(std::string const &key);
void broadcast(tcp_operation_header &h,std::string &data);
public:
tcp_connector(std::vector<std::string> const &ip_list,std::vector<int> const &port_list);

virtual ~tcp_connector();
};


}


#endif

+ 60
- 0
tcp_messenger.cpp View File

@@ -0,0 +1,60 @@
#include "tcp_messenger.h"

namespace cppcms {

void messenger::connect(string ip,int port)
{
ip_=ip;
port_=port;
error_code e;
socket_.connect(tcp::endpoint(aio::ip::address::from_string(ip),port),e);
if(e) throw cppcms_error("connect:"+e.message());
tcp::no_delay nd(true);
socket_.set_option(nd);
}

messenger::messenger(string ip,int port) :
socket_(srv_)
{
connect(ip,port);
}
messenger::messenger() :
socket_(srv_)
{
}

void messenger::transmit(tcp_operation_header &h,string &data)
{
bool done=false;
int times=0;
do {
try {
aio::write(socket_,aio::buffer(&h,sizeof(h)));
if(h.size>0) {
aio::write(socket_,aio::buffer(data,h.size));
}
aio::read(socket_,aio::buffer(&h,sizeof(h)));
if(h.size>0) {
vector<char> d(h.size);
aio::read(socket_,aio::buffer(d,h.size));
data.assign(d.begin(),d.begin()+h.size);
}
done=true;
}
catch(system_error const &e) {
if(times) {
throw cppcms_error(string("tcp_cache:")+e.what());
}
socket_.close();
error_code er;
socket_.connect(
tcp::endpoint(
aio::ip::address::from_string(ip_),port_),er);
if(er) throw cppcms_error("reconnect:"+er.message());
times++;
}
}while(!done);
}
} // namespace cppcms


+ 44
- 0
tcp_messenger.h View File

@@ -0,0 +1,44 @@
#ifndef CPPCMS_TCP_MESSENGER_H
#define CPPCMS_TCP_MESSENGER_H

#include "config.h"
#ifdef __CYGWIN__
// Cygwin ASIO works only with win32 sockets
#define _WIN32_WINNT 1
#define __USE_W32_SOCKETS 1
#endif

#ifdef USE_BOOST_ASIO
#include <boost/asio.hpp>
namespace aio = boost::asio;
using boost::system::error_code;
using boost::system::system_error;
#else
#include <asio.hpp>
namespace aio = asio;
using asio::error_code;
using asio::system_error;
#endif

#include "tcp_cache_protocol.h"
#include "archive.h"

using aio::ip::tcp;

namespace cppcms {

class messenger : boost::noncopyable {
aio::io_service srv_;
tcp::socket socket_;
string ip_;
int port_;
public:
void connect(string ip,int port);
messenger(string ip,int port);
messenger();
void transmit(tcp_operation_header &h,string &data);
};

} // cppcms

#endif

+ 7
- 2
worker_thread.cpp View File

@@ -15,7 +15,10 @@ worker_thread::worker_thread(manager const &s) :
url(this),
app(s),
cache(this),
cout(&(this->out_buf))
cout(&(this->out_buf)),
on_start(),
on_end(),
session(*this)
{
caching_module=app.cache->get();
static const transtext::trans tr;
@@ -87,7 +90,9 @@ void worker_thread::run(cgicc_connection &cgi_conn)

try {
/**********/
session.on_start();
main();
session.on_end();
/**********/
if(response_header.get() == NULL) {
throw cppcms_error("Looks like a bug");
@@ -147,7 +152,7 @@ void worker_thread::render(string tmpl,string name,base_content &content,ostream
base_view::settings s(this,&out);
auto_ptr<base_view> p(views_storage::instance().fetch_view(tmpl,name,s,&content));
if(!p.get()) throw cppcms_error("Template `"+name+"' not found in template set `" + tmpl +"'");
p->render();
p->render();
};

void worker_thread::render(string tmpl,string name,base_content &content)


+ 6
- 5
worker_thread.h View File

@@ -19,6 +19,7 @@
#include "base_cache.h"
#include "cgicc_connection.h"
#include "transtext.h"
#include "session_interface.h"

namespace cppcms {

@@ -46,12 +47,12 @@ class worker_thread: private boost::noncopyable {

transtext::trans const *gt;
string lang;
auto_ptr<HTTPHeader> response_header;
string current_template;
public:
url_parser url;
manager const &app;
Cgicc *cgi;
@@ -60,9 +61,9 @@ public:

cache_iface cache;
ostream cout;

boost::signal<void()> on_start;
boost::signal<void()> on_end;
session_interface session;

void set_header(HTTPHeader *h);
void add_header(string s);
@@ -86,7 +87,7 @@ public:
inline char const *ngettext(char const *s,char const *p,int n) { return gt->ngettext(s,p,n); };

ostream &get_cout() { return cout; }
transtext::trans const *domain_gettext(string const &domain);

void run(cgicc_connection &);


Loading…
Cancel
Save