@@ -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 | |||
@@ -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,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 | |||
@@ -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) | |||
@@ -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); } | |||
@@ -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 |
@@ -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; | |||
@@ -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); | |||
@@ -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 } | |||
@@ -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" | |||
], | |||
@@ -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> |
@@ -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) {}; | |||
}; | |||
@@ -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,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++) | |||
@@ -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++) | |||
@@ -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); | |||
} | |||
@@ -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",""), | |||
@@ -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); | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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(); | |||
} | |||
}; |
@@ -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 |
@@ -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(); | |||
} | |||
}; | |||
@@ -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 |
@@ -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 |
@@ -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,×tamp,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 ×tamp,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 |
@@ -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 ×tamp,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 |
@@ -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; | |||
} | |||
}; | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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; | |||
}; | |||
@@ -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; | |||
@@ -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 | |||
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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) | |||
@@ -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 &); | |||