From 62d9eb0befe8da1109dcd47b4b91480cb8b28bf8 Mon Sep 17 00:00:00 2001 From: zhengchao Date: Fri, 9 Nov 2018 15:52:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=BC=93=E5=AD=98=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E5=92=8C=E5=8F=AF=E4=BF=A1=E8=AF=81=E4=B9=A6=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E7=9A=84=E5=BC=80=E5=8F=91=EF=BC=8C=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/src/tango_cache_pending.cpp | 11 +- conf/pangu/table_info.conf | 18 +- plugin/business/pangu-http/CMakeLists.txt | 2 +- plugin/business/pangu-http/src/pangu_http.cpp | 158 ++++- .../pangu-http/src/pangu_web_cache.cpp | 560 ++++++++++++++---- .../business/pangu-http/src/pangu_web_cache.h | 12 +- vendor/CMakeLists.txt | 18 + vendor/dablooms-0.9.1.tar.gz | Bin 0 -> 25960 bytes 8 files changed, 628 insertions(+), 151 deletions(-) create mode 100644 vendor/dablooms-0.9.1.tar.gz diff --git a/cache/src/tango_cache_pending.cpp b/cache/src/tango_cache_pending.cpp index 2c7ea19..ab9b69b 100644 --- a/cache/src/tango_cache_pending.cpp +++ b/cache/src/tango_cache_pending.cpp @@ -284,15 +284,14 @@ enum cache_pending_action tfe_cache_put_pending(const struct tfe_http_half *resp memset(freshness,0,sizeof(struct response_freshness)); if(response->resp_spec.resp_code!=TFE_HTTP_STATUS_OK || NULL!=tfe_http_std_field_read(response, TFE_HTTP_CONT_RANGE) //NOT upload response with content-range - || NULL==response->resp_spec.content_length) - { - return FORBIDDEN; - } - value=tfe_http_std_field_read(response, TFE_HTTP_SET_COOKIE); - if(value!=NULL) + || NULL==response->resp_spec.content_length + || NULL!=tfe_http_std_field_read(response, TFE_HTTP_AUTHORIZATION) + || NULL!=tfe_http_nonstd_field_read(response, "WWW-Authenticate") + || NULL!=tfe_http_std_field_read(response, TFE_HTTP_SET_COOKIE)) { return FORBIDDEN; } + value = tfe_http_std_field_read(response, TFE_HTTP_PRAGMA); if (value != NULL) { diff --git a/conf/pangu/table_info.conf b/conf/pangu/table_info.conf index 043accc..7c5a34d 100644 --- a/conf/pangu/table_info.conf +++ b/conf/pangu/table_info.conf @@ -11,15 +11,21 @@ #id name type # #For plugin table -#id name type valid_column +#id name type json_descr # #For expr/expr_plus Table #id name type src_charset dst_charset do_merge cross_cache quick_mode 0 PXY_CTRL_COMPILE compile escape -- 1 PXY_CTRL_GROUP group -- 2 PXY_CTRL_IP ip --- -3 PXY_CTRL_HTTP_URL expr UTF8 GBK/BIG5/UNICODE/UTF8/url_encode_gb2312/url_encode_utf8 yes 128 quickoff -4 PXY_CTRL_HTTP_REQ_HDR expr_plus UTF8 GBK/BIG5/UNICODE/UTF8/url_encode_gb2312/url_encode_utf8 yes 128 quickoff -5 PXY_CTRL_HTTP_REQ_BODY expr UTF8 GBK/BIG5/UNICODE/UTF8/url_encode_gb2312/url_encode_utf8 yes 128 quickoff -6 PXY_CTRL_HTTP_RES_HDR expr_plus UTF8 GBK/BIG5/UNICODE/UTF8/url_encode_gb2312/url_encode_utf8 yes 128 quickoff -7 PXY_CTRL_HTTP_RES_BODY expr UTF8 GBK/BIG5/UNICODE/UTF8/url_encode_gb2312/url_encode_utf8 yes 128 quickoff +3 PXY_CTRL_HTTP_URL expr UTF8 GBK/UNICODE/UTF8/url_encode_gb2312/url_encode_utf8 yes 0 quickoff +4 PXY_CTRL_HTTP_REQ_HDR expr_plus UTF8 UTF8 yes 0 quickoff +5 PXY_CTRL_HTTP_REQ_BODY expr UTF8 GBK/BIG5/UNICODE/UTF8 yes 128 quickoff +6 PXY_CTRL_HTTP_RES_HDR expr_plus UTF8 UTF8 UTF8 yes 0 quickoff +7 PXY_CTRL_HTTP_RES_BODY expr UTF8 GBK/BIG5/UNICODE/UTF8 yes 128 quickoff +8 PXY_CACHE_COMPILE compile escape -- +9 PXY_CACHE_GROUP group -- +10 PXY_CACHE_HTTP_URL expr UTF8 UTF8 yes 0 quickoff +11 PXY_CACHE_HTTP_COOKIE expr UTF8 UTF8 yes 0 quickoff +12 PXY_OBJ_TRUSTED_CA_CERT plugin {"valid":4,"foreign":"3"} +13 PXY_OBJ_TRUSTED_CA_CRL plugin {"valid":4,"foreign":"3"} \ No newline at end of file diff --git a/plugin/business/pangu-http/CMakeLists.txt b/plugin/business/pangu-http/CMakeLists.txt index 2b93b94..a11a0fe 100644 --- a/plugin/business/pangu-http/CMakeLists.txt +++ b/plugin/business/pangu-http/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(pangu-http src/pangu_logger.cpp src/pangu_http.cpp src/pattern_replace.cpp src/pangu_web_cache.cpp) target_link_libraries(pangu-http PUBLIC common http tango-cache-client) -target_link_libraries(pangu-http PUBLIC librdkafka ctemplate-static cjson pcre2-static pthread) +target_link_libraries(pangu-http PUBLIC librdkafka ctemplate-static cjson pcre2-static libdablooms pthread) target_link_libraries(pangu-http PUBLIC maatframe) add_executable(test_pattern_replace src/test_pattern_replace.cpp src/pattern_replace.cpp) diff --git a/plugin/business/pangu-http/src/pangu_http.cpp b/plugin/business/pangu-http/src/pangu_http.cpp index d631749..38ae4d9 100644 --- a/plugin/business/pangu-http/src/pangu_http.cpp +++ b/plugin/business/pangu-http/src/pangu_http.cpp @@ -85,6 +85,8 @@ struct pangu_rt int fs_id[__PG_STAT_MAX]; struct event_base* gc_evbase; struct event* gcev; + + int ca_store_reseting; }; struct pangu_rt * g_pangu_rt; @@ -206,11 +208,111 @@ static void pangu_http_stat_init(struct pangu_rt * pangu_runtime) return; } +void trusted_CA_update_start_cb(int update_type, void* u_para) +{ + if(update_type==MAAT_RULE_UPDATE_TYPE_FULL) + { + if(g_pangu_rt->ca_store_reseting==0) + { + tfe_proxy_ssl_reset_trust_ca(); + TFE_LOG_INFO(g_pangu_rt->local_logger, "Trusted CA Store Reset Start."); + } + g_pangu_rt->ca_store_reseting++; + } + +} +void trusted_CA_update_cert_cb(int table_id, const char* table_line, void* u_para) +{ + int ret=0, cfg_id=0, is_valid=0; + char cert_name[128]={0}, cert_file[1024]={0}; + ret=sscanf(table_line, "%d\t%s\t%s\t%d", &cfg_id, cert_name, cert_file, &is_valid); + if(ret!=4) + { + TFE_LOG_ERROR(g_pangu_rt->local_logger, "Trusted CA Store parse cert config failed: %s", table_line); + return; + } + if(is_valid==1) + { + ret=tfe_proxy_ssl_add_trust_ca(cert_file); + if(ret<0) + { + TFE_LOG_ERROR(g_pangu_rt->local_logger, "Trusted CA Store add cert failed %d:%s:%s", cfg_id, cert_name, cert_file); + } + else + { + TFE_LOG_INFO(g_pangu_rt->local_logger, "Trusted CA Store add cert success %d:%s:%s", cfg_id, cert_name, cert_file); + } + } + else + { + ret=tfe_proxy_ssl_del_trust_ca(cert_file); + if(ret<0) + { + TFE_LOG_ERROR(g_pangu_rt->local_logger, "Trusted CA Store del cert failed %d:%s:%s", cfg_id, cert_name, cert_file); + } + else + { + TFE_LOG_INFO(g_pangu_rt->local_logger, "Trusted CA Store del cert success %d:%s:%s", cfg_id, cert_name, cert_file); + } + } + return; +} +void trusted_CA_update_crl_cb(int table_id,const char* table_line,void* u_para) +{ + int ret=0, crl_id=0, cert_id=0, is_valid=0; + char crl_file[1024]={0}; + ret=sscanf(table_line, "%d\t%d\t%s\t%d", &crl_id, &cert_id, crl_file, &is_valid); + if(ret!=4) + { + TFE_LOG_ERROR(g_pangu_rt->local_logger, "Trusted CA Store parse crl config failed: %s", table_line); + return; + } + if(is_valid==1) + { + ret=tfe_proxy_ssl_add_crl(crl_file); + if(ret<0) + { + TFE_LOG_ERROR(g_pangu_rt->local_logger, "Trusted CA Store add crl failed %d:%s:%s", crl_id, cert_id, crl_file); + } + else + { + TFE_LOG_INFO(g_pangu_rt->local_logger, "Trusted CA Store add crl success %d:%d:%s", crl_id, cert_id, crl_file); + } + } + else + { + ret=tfe_proxy_ssl_del_crl(crl_file); + if(ret<0) + { + TFE_LOG_ERROR(g_pangu_rt->local_logger, "Trusted CA Store del crl failed %d:%s:%s", crl_id, cert_id, crl_file); + } + else + { + TFE_LOG_INFO(g_pangu_rt->local_logger, "Trusted CA Store del crl success %d:%d:%s", crl_id, cert_id, crl_file); + } + } + return; +} +void trusted_CA_update_finish_cb(void* u_para) +{ + int is_last_updating_table=0, ret=0; + if(g_pangu_rt->ca_store_reseting>0) + { + g_pangu_rt->ca_store_reseting--; + if(g_pangu_rt->ca_store_reseting==0) + { + TFE_LOG_INFO(g_pangu_rt->local_logger, "Trusted CA Store Reset Finish."); + } + } +} + int pangu_http_init(struct tfe_proxy * proxy) { const char * profile = "./pangu_conf/pangu_pxy.conf"; - const char * logfile = "./log/pangu_pxy.log"; + const char * logfile = "./log/pangu_pxy.log"; + int table_id=0; + g_pangu_rt = ALLOC(struct pangu_rt, 1); g_pangu_rt->thread_num = tfe_proxy_get_work_thread_count(); g_pangu_rt->gc_evbase=tfe_proxy_get_gc_evbase(); @@ -266,7 +368,8 @@ int pangu_http_init(struct tfe_proxy * proxy) MESA_load_profile_int_def(profile, "TANGO_CACHE", "enable_cache", &(g_pangu_rt->cache_enabled), 1); if(g_pangu_rt->cache_enabled) { - g_pangu_rt->cache = create_web_cache_handle(profile, "TANGO_CACHE", g_pangu_rt->gc_evbase, g_pangu_rt->local_logger); + g_pangu_rt->cache = create_web_cache_handle(profile, "TANGO_CACHE", g_pangu_rt->gc_evbase, + g_pangu_rt->maat, g_pangu_rt->local_logger); if(!g_pangu_rt->cache) { TFE_LOG_INFO(NULL, "Tango Cache init failed."); @@ -274,6 +377,30 @@ int pangu_http_init(struct tfe_proxy * proxy) } TFE_LOG_INFO(NULL, "Tango Cache Enabled."); } + table_id=Maat_table_register(g_pangu_rt->maat, "PXY_OBJ_TRUSTED_CA_CERT"); + if(table_id<0) + { + TFE_LOG_INFO(NULL, "Pangu HTTP register table PXY_OBJ_TRUSTED_CA_CERT failed."); + goto error_out; + } + Maat_table_callback_register(g_pangu_rt->maat, table_id, + trusted_CA_update_start_cb, + trusted_CA_update_cert_cb, + trusted_CA_update_finish_cb, + g_pangu_rt); + + table_id=Maat_table_register(g_pangu_rt->maat, "PXY_OBJ_TRUSTED_CA_CRL"); + if(table_id<0) + { + TFE_LOG_INFO(NULL, "Pangu HTTP register table PXY_OBJ_TRUSTED_CA_CRL failed."); + goto error_out; + } + Maat_table_callback_register(g_pangu_rt->maat, table_id, + trusted_CA_update_start_cb, + trusted_CA_update_crl_cb, + trusted_CA_update_finish_cb, + g_pangu_rt); + TFE_LOG_INFO(NULL, "Pangu HTTP init success."); return 0; @@ -297,9 +424,9 @@ struct pangu_http_ctx int magic_num; enum pangu_action action; char * action_para; - scan_status_t mid; + scan_status_t scan_mid; stream_para_t sp; - + struct cache_mid* cmid; struct Maat_rule_t * enforce_rules; size_t n_enforce; char * enforce_para; @@ -345,7 +472,7 @@ static struct pangu_http_ctx * pangu_http_ctx_new(unsigned int thread_id) { struct pangu_http_ctx * ctx = ALLOC(struct pangu_http_ctx, 1); ctx->magic_num=HTTP_CTX_MAGIC_NUM; - ctx->mid = NULL; + ctx->scan_mid = NULL; ctx->thread_id = (int) thread_id; return ctx; } @@ -360,8 +487,8 @@ static void pangu_http_ctx_free(struct pangu_http_ctx * ctx) } FREE(&ctx->enforce_rules); FREE(&ctx->enforce_para); - Maat_clean_status(&(ctx->mid)); - ctx->mid = NULL; + Maat_clean_status(&(ctx->scan_mid)); + ctx->scan_mid = NULL; if(ctx->sp) { @@ -749,7 +876,7 @@ enum pangu_action http_scan(const struct tfe_http_session * session, enum tfe_ht int str_url_length = (int) (strlen(session->req->req_spec.url)); scan_ret = Maat_full_scan_string(g_pangu_rt->maat, g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_URL], - CHARSET_UTF8, str_url, str_url_length, result, NULL, MAX_SCAN_RESULT, &(ctx->mid), ctx->thread_id); + CHARSET_UTF8, str_url, str_url_length, result, NULL, MAX_SCAN_RESULT, &(ctx->scan_mid), ctx->thread_id); if (scan_ret > 0) { @@ -770,13 +897,13 @@ enum pangu_action http_scan(const struct tfe_http_session * session, enum tfe_ht } const char * str_field_name = http_field_to_string(&field_name); - scan_ret = Maat_set_scan_status(g_pangu_rt->maat, &(ctx->mid), MAAT_SET_SCAN_DISTRICT, + scan_ret = Maat_set_scan_status(g_pangu_rt->maat, &(ctx->scan_mid), MAAT_SET_SCAN_DISTRICT, str_field_name, strlen(str_field_name)); assert(scan_ret == 0); scan_ret = Maat_full_scan_string(g_pangu_rt->maat, table_id, CHARSET_UTF8, field_val, strlen(field_val), - result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->mid), ctx->thread_id); + result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->scan_mid), ctx->thread_id); if (scan_ret > 0) { hit_cnt += scan_ret; @@ -795,7 +922,7 @@ enum pangu_action http_scan(const struct tfe_http_session * session, enum tfe_ht if (body_frag != NULL) { scan_ret = Maat_stream_scan_string(&(ctx->sp), CHARSET_UTF8, (const char *) body_frag, (int) frag_size, - result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->mid)); + result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->scan_mid)); if (scan_ret > 0) { hit_cnt += scan_ret; @@ -1057,7 +1184,7 @@ void cache_pending(const struct tfe_http_session * session, unsigned int thread_ enum cache_pending_result ret; ctx->f_cache_pending=future_create("cache_pend", cache_pending_on_succ, cache_pending_on_fail, ctx); ctx->ref_session=tfe_http_session_allow_write(session); - ctx->pending_result=web_cache_async_pending(g_pangu_rt->cache, thread_id, session->req, ctx->f_cache_pending); + ctx->pending_result=web_cache_async_pending(g_pangu_rt->cache, thread_id, session->req, &(ctx->cmid), ctx->f_cache_pending); switch(ctx->pending_result) { case PENDING_RESULT_REVALIDATE: @@ -1077,7 +1204,7 @@ void cache_pending(const struct tfe_http_session * session, unsigned int thread_ void cache_query(const struct tfe_http_session * session, unsigned int thread_id, struct pangu_http_ctx * ctx) { ctx->f_cache_query=future_create("cache_get", cache_query_on_succ, cache_query_on_fail, ctx); - int ret=web_cache_async_query(g_pangu_rt->cache, thread_id, session->req, ctx->f_cache_query); + int ret=web_cache_async_query(g_pangu_rt->cache, thread_id, session->req, &(ctx->cmid), ctx->f_cache_query); if(ret==0) { ctx->ref_session=tfe_http_session_allow_write(session); @@ -1097,7 +1224,7 @@ void cache_update(const struct tfe_http_session * session, enum tfe_http_event e if(events & EV_HTTP_RESP_BODY_BEGIN) { - ctx->cache_update_ctx=web_cache_update_start(g_pangu_rt->cache, thread_id, session); + ctx->cache_update_ctx=web_cache_update_start(g_pangu_rt->cache, thread_id, session, &(ctx->cmid)); } if(events & EV_HTTP_RESP_BODY_CONT && ctx->cache_update_ctx!=NULL) { @@ -1124,7 +1251,7 @@ void pangu_on_http_begin(const struct tfe_stream * stream, ctx = pangu_http_ctx_new(thread_id); addr_tfe2sapp(stream->addr, &sapp_addr); hit_cnt = Maat_scan_proto_addr(g_pangu_rt->maat, g_pangu_rt->scan_table_id[PXY_CTRL_IP], &sapp_addr, 0, - result, MAX_SCAN_RESULT, &(ctx->mid), (int) thread_id); + result, MAX_SCAN_RESULT, &(ctx->scan_mid), (int) thread_id); if (hit_cnt > 0) { @@ -1174,6 +1301,7 @@ void pangu_on_http_end(const struct tfe_stream * stream, { ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REPLACE])); } + cache_mid_clear(&(ctx->cmid)); pangu_http_ctx_free(ctx); *pme = NULL; return; diff --git a/plugin/business/pangu-http/src/pangu_web_cache.cpp b/plugin/business/pangu-http/src/pangu_web_cache.cpp index 1468805..1e01395 100644 --- a/plugin/business/pangu-http/src/pangu_web_cache.cpp +++ b/plugin/business/pangu-http/src/pangu_web_cache.cpp @@ -9,11 +9,19 @@ #include #include -#include #include #include +extern "C" +{ +#include +} + +#include + +#include + enum cache_stat_field { STAT_CACHE_QUERY, @@ -29,6 +37,7 @@ enum cache_stat_field STAT_CACHE_QUERYING, STAT_CACHE_PENDING, STAT_CACHE_UPLOAD_CNT, + STAT_CACHE_UPLOAD_BYPASS, STAT_CACHE_UPLOAD_OVERRIDE, STAT_CACHE_UPLOAD_FORBIDEN, STAT_CACHE_UPLOAD_ABANDON, @@ -44,6 +53,33 @@ enum cache_stat_field STAT_CACHE_OVERRIDE_UPLOAD_OBJ_SIZE, __CACHE_STAT_MAX }; + +struct cache_key_descr +{ + int is_not_empty; + size_t qs_num; + char** ignore_qs; + char* include_cookie; +}; +struct cache_param +{ + int ref_cnt; + struct cache_key_descr cache_key; + + char no_revalidate; + char cache_dyn_url; + char cache_cookied_cont; + char ignore_req_nocache; + char ignore_res_nocache; + char force_caching; + + int min_use; + time_t pinning_time_sec; + time_t inactive_time_sec; + long max_cache_size; + long max_cache_obj_size; + pthread_mutex_t lock; +}; struct cache_handle { unsigned int thread_count; @@ -62,11 +98,16 @@ struct cache_handle struct event_base* gc_evbase; struct event* gcev; + + int cache_policy_enabled; //otherwise use default cache policy struct cache_param default_cache_policy; Maat_feather_t ref_feather; int cache_param_idx; int table_url_constraint; int table_cookie_constraint; + size_t cache_key_bloom_size; + int cache_key_bloom_life; + counting_bloom_t **cache_key_bloom; void* logger; }; struct cache_update_context @@ -74,7 +115,7 @@ struct cache_update_context struct cache_handle* ref_cache_handle; struct tango_cache_ctx * write_ctx; }; -static void cache_gc_cb(evutil_socket_t fd, short what, void * arg) +static void web_cache_stat_cb(evutil_socket_t fd, short what, void * arg) { struct cache_handle* cache=(struct cache_handle *)arg; struct cache_statistics client_stat_sum, client_stat; @@ -169,6 +210,7 @@ void cache_stat_init(struct cache_handle* cache) set_stat_spec(&spec[STAT_CACHE_QUERYING], "getting",FS_STYLE_STATUS, FS_CALC_CURRENT); set_stat_spec(&spec[STAT_CACHE_PENDING], "pending",FS_STYLE_STATUS, FS_CALC_CURRENT); set_stat_spec(&spec[STAT_CACHE_UPLOAD_CNT], "cache_put",FS_STYLE_FIELD, FS_CALC_CURRENT); + set_stat_spec(&spec[STAT_CACHE_UPLOAD_BYPASS], "cache_bypass",FS_STYLE_FIELD, FS_CALC_CURRENT); set_stat_spec(&spec[STAT_CACHE_UPLOAD_OVERRIDE], "or_put",FS_STYLE_FIELD, FS_CALC_CURRENT); set_stat_spec(&spec[STAT_CACHE_UPLOAD_FORBIDEN], "put_forbid",FS_STYLE_FIELD, FS_CALC_CURRENT); set_stat_spec(&spec[STAT_CACHE_UPLOAD_ABANDON], "put_abandon",FS_STYLE_FIELD, FS_CALC_CURRENT); @@ -214,44 +256,170 @@ void cache_stat_init(struct cache_handle* cache) FS_start(cache->fs_handle); struct timeval gc_delay = {0, 500*1000}; //Microseconds, we set 500 miliseconds here. - cache->gcev = event_new(cache->gc_evbase, -1, EV_PERSIST, cache_gc_cb, cache); + cache->gcev = event_new(cache->gc_evbase, -1, EV_PERSIST, web_cache_stat_cb, cache); evtimer_add(cache->gcev, &gc_delay); return; } -struct cache_key_descr -{ - size_t qs_num; - char** ignore_qs; - char* include_hdrs; - char* include_cookie; -}; -struct cache_param -{ - struct cache_key_descr key_descr; - - char revalidate; - char cache_dyn_url; - char cache_cookied_cont; - char ignore_req_nocache; - char ignore_res_nocache; - char force_caching; - - int min_use; - int pinning_time_sec; - int inactive_time_sec; - int max_cache_size_mb; - int max_cache_obj_size_mb; -}; -int time_secs_interval(const char* str){} -int storage_mb_size(const char* str){} -int is_dynamic_url(const char* url){} +time_t time_unit_sec(const char* str) +{ + time_t value=0; + sscanf(str, "%ld", &value); + switch(str[strlen(str)-1]) + { + case 's': + break; + case 'm': + value*=60; + break; + case 'h': + value*=3600; + break; + case 'd': + value*=24*3600; + break; + default: + break; + } + return value; +} +size_t storage_unit_byte(const char* str) +{ + size_t value=0; + sscanf(str, "%ld", &value); + switch(str[strlen(str)-1]) + { + case 'b': + break; + case 'k': + value*=1024; + break; + case 'm': + value*=1024*1024; + break; + case 'g': + value*=1024*1024*1024; + break; + case 't': + if(value<1024) + { +#pragma GCC diagnostic ignored "-Woverflow" + value*=1024*1024*1024*1024; + + } + break; + default: + break; + } + return value; +} + + +//A URL is considered dynamic if it ends in “.asp(x)” or contains a question mark (?), a semicolon (;), or “cgi”. +char is_dynamic_url(const char* url) +{ + if(strchr(url, '?') + || strchr(url, ';') + || strstr(url, "cgi") + || 0==strcmp(url+strlen(url)-3,"asp") + || 0==strcmp(url+strlen(url)-4, "aspx")) + { + return 1; + } + return 0; +} +char * cookie_scanvalue(const char * key, const char * qs, char * val, size_t val_len) +{ + int i=0, key_len=0; + + key_len = strlen(key); + while(qs[0] != '\0') + { + if ( strncmp(key, qs, key_len) == 0 ) + break; + qs += strcspn(qs, ";") + 1; + } + + if ( qs[0] == '\0' ) return NULL; + + qs += strcspn(qs, "=;"); + if ( qs[0] == '=' ) + { + qs++; + i = strcspn(qs, "=;"); + strncpy(val, qs, (val_len-1)<(i+1) ? (val_len-1) : (i+1)); + } + else + { + if ( val_len > 0 ) + val[0] = '\0'; + } + + return val; +} + + +char* get_cache_key(const struct tfe_http_half * request, const struct cache_key_descr* desc) +{ + if(desc==NULL|| !desc->is_not_empty) + { + return NULL; + } + int i=0, shall_ignore=0; + + char *token=NULL,*sub_token=NULL,*saveptr; + char* url=tfe_strdup(request->req_spec.url); + const char* cookie=NULL; + char cookie_val[256]={0}; //most 256 bytes for cookie key + size_t key_size=strlen(url)+sizeof(cookie_val); + char* cache_key=ALLOC(char, key_size); + char* query_string=strchr(url, '?'); + if(query_string!=NULL && desc->qs_num>0) + { + query_string++; + for (token = url; ; token= NULL) + { + sub_token= strtok_r(token,"&", &saveptr); + if (sub_token == NULL) + break; + shall_ignore=0; + for(i=0; iqs_num; i++) + { + if(0==strncasecmp(sub_token, desc->ignore_qs[i], strlen(desc->ignore_qs[i]))) + { + shall_ignore=1; + break; + } + } + if(!shall_ignore) + { + strncat(cache_key, sub_token, key_size); + } + } + } + else + { + strncat(cache_key, url, key_size); + } + if(desc->include_cookie && (cookie=tfe_http_std_field_read(request, TFE_HTTP_COOKIE))!=NULL) + { + cookie_scanvalue(desc->include_cookie, cookie, cookie_val, sizeof(cookie_val)); + if(strlen(cookie_val)>0) + { + strncat(cache_key, cookie_val, key_size); + } + } + FREE(&(url)); + +} + void cache_param_new(int idx, const struct Maat_rule_t* rule, const char* srv_def_large, MAAT_RULE_EX_DATA* ad, long argl, void *argp) { struct cache_handle* cache=(struct cache_handle*) argp; int i=0; + size_t len=0; *ad=NULL; if(rule->serv_def_lendefault_cache_policy; + param->ref_cnt=1; + pthread_mutex_init(&(param->lock), NULL); key_desc=cJSON_GetObjectItem(json,"cache_key"); if(key_desc && key_desc->type==cJSON_Object) { + param->cache_key.is_not_empty=1; qs=cJSON_GetObjectItem(json,"ignore_qs"); if(qs && qs->type==cJSON_Array) { - param->key_descr.qs_num=cJSON_GetArraySize(qs); - param->key_descr.ignore_qs=ALLOC(char*, param->key_descr.qs_num); - for(i=0; ikey_descr.qs_num; i++) + param->cache_key.qs_num=cJSON_GetArraySize(qs); + param->cache_key.ignore_qs=ALLOC(char*, param->cache_key.qs_num); + for(i=0; icache_key.qs_num; i++) { item=cJSON_GetArrayItem(item, i); - param->key_descr.ignore_qs[i]=tfe_strdup(qs->valuestring); + len=strlen(qs->valuestring)+2; + param->cache_key.ignore_qs[i]=ALLOC(char, len); + strncat(param->cache_key.ignore_qs[i], qs->valuestring, len); + strncat(param->cache_key.ignore_qs[i], "=", len); } - } - } - item=cJSON_GetObjectItem(key_desc,"hdrs"); - if(item && item->type==cJSON_String) param->key_descr.include_hdrs=tfe_strdup(item->valuestring); + } + item=cJSON_GetObjectItem(key_desc,"cookie"); + if(item && item->type==cJSON_String) param->cache_key.include_cookie=tfe_strdup(param->cache_key.include_cookie); + + } - item=cJSON_GetObjectItem(key_desc,"cookie"); - if(item && item->type==cJSON_String) param->key_descr.include_cookie=tfe_strdup(item->valuestring); - item=cJSON_GetObjectItem(json,"revalidate"); - if(item && item->type==cJSON_Number) param->revalidate=item->valueint; + item=cJSON_GetObjectItem(json,"no_revalidate"); + if(item && item->type==cJSON_Number) param->no_revalidate=item->valueint; item=cJSON_GetObjectItem(json,"cache_dyn_url"); if(item && item->type==cJSON_Number) param->cache_dyn_url=item->valueint; @@ -310,16 +484,16 @@ void cache_param_new(int idx, const struct Maat_rule_t* rule, const char* srv_de if(item && item->type==cJSON_Number) param->min_use=item->valueint; item=cJSON_GetObjectItem(json,"pinning_time"); - if(item && item->type==cJSON_String) param->pinning_time_sec=time_secs_interval(item->valuestring); + if(item && item->type==cJSON_String) param->pinning_time_sec=time_unit_sec(item->valuestring); item=cJSON_GetObjectItem(json,"inactive_time"); - if(item && item->type==cJSON_String) param->inactive_time_sec=time_secs_interval(item->valuestring); + if(item && item->type==cJSON_String) param->inactive_time_sec=time_unit_sec(item->valuestring); item=cJSON_GetObjectItem(json,"max_cache_size"); - if(item && item->type==cJSON_String) param->max_cache_size_mb=storage_mb_size(item->valuestring); + if(item && item->type==cJSON_String) param->max_cache_size=storage_unit_byte(item->valuestring); item=cJSON_GetObjectItem(json,"max_cache_obj_size"); - if(item && item->type==cJSON_String) param->max_cache_obj_size_mb=storage_mb_size(item->valuestring); + if(item && item->type==cJSON_String) param->max_cache_obj_size=storage_unit_byte(item->valuestring); cJSON_Delete(json); return; @@ -332,28 +506,73 @@ void cache_param_free(int idx, const struct Maat_rule_t* rule, const char* srv_d return; } struct cache_param* param=(struct cache_param*)*ad; - for(i=0; ikey_descr.qs_num; i++) - { - FREE(&(param->key_descr.ignore_qs[i])); + pthread_mutex_lock(&(param->lock)); + param->ref_cnt--; + if(param->ref_cnt>0) + { + pthread_mutex_unlock(&(param->lock)); + return; } - FREE(&(param->key_descr.ignore_qs)); - FREE(&(param->key_descr.include_cookie)); - FREE(&(param->key_descr.include_hdrs)); + pthread_mutex_unlock(&(param->lock)); + pthread_mutex_destroy(&(param->lock)); + for(i=0; icache_key.qs_num; i++) + { + FREE(&(param->cache_key.ignore_qs[i])); + } + FREE(&(param->cache_key.ignore_qs)); + FREE(&(param->cache_key.include_cookie)); FREE(&(param)); return; } +void cache_param_dup(int idx, MAAT_RULE_EX_DATA *to, MAAT_RULE_EX_DATA *from, long argl, void *argp) +{ + struct cache_param* from_param=*((struct cache_param**)from); + pthread_mutex_lock(&(from_param->lock)); + from_param->ref_cnt++; + pthread_mutex_unlock(&(from_param->lock)); + *((struct cache_param**)to)=from_param; + return; +} + +static void cache_key_bloom_gc_cb(evutil_socket_t fd, short what, void * arg) +{ + counting_bloom_t* old_bloom=*((counting_bloom_t**)arg), *new_bloom=NULL; + + new_bloom=new_counting_bloom(old_bloom->capacity, old_bloom->error_rate, NULL); + free_counting_bloom(old_bloom); + *((counting_bloom_t**)arg)=old_bloom; + return; +} struct cache_handle* create_web_cache_handle(const char* profile_path, const char* section, struct event_base* gc_evbase, Maat_feather_t feather, void *logger) { struct cache_handle* cache=ALLOC(struct cache_handle, 1); int temp=0; + struct event* ev=NULL; cache->logger=logger; cache->thread_count=tfe_proxy_get_work_thread_count(); cache->clients=ALLOC(struct tango_cache_instance *, cache->thread_count); + cache->cache_key_bloom=ALLOC(counting_bloom_t*, cache->thread_count); + MESA_load_profile_int_def(profile_path, section, "cache_policy_enabled", + &(cache->cache_policy_enabled), 1); + + + MESA_load_profile_int_def(profile_path, section, "cache_key_bloom_size", + (int*)&(cache->cache_key_bloom_size), 16*1000*1000); + MESA_load_profile_int_def(profile_path, section, "cache_key_bloom_life", + &(cache->cache_key_bloom_life), 30*60); + struct timeval gc_refresh_delay = {cache->cache_key_bloom_life, 0}; int i=0; for(i=0; ithread_count; i++) { + if(cache->cache_policy_enabled) + { + cache->cache_key_bloom[i]=new_counting_bloom(cache->cache_key_bloom_size, 0.01, NULL); + ev = event_new(tfe_proxy_get_work_thread_evbase(i), -1, EV_PERSIST, cache_key_bloom_gc_cb, &(cache->cache_key_bloom[i])); + evtimer_add(ev, &gc_refresh_delay); + } + cache->clients[i]=tango_cache_instance_new(tfe_proxy_get_work_thread_evbase(i), profile_path, section, logger); if(cache->clients[i]==NULL) { @@ -369,29 +588,32 @@ struct cache_handle* create_web_cache_handle(const char* profile_path, const cha MESA_load_profile_int_def(profile_path, section, "cache_undefined_obj", &(cache->cache_undefined_obj_enabled), 1); MESA_load_profile_int_def(profile_path, section, "cached_undefined_obj_minimum_size", &(temp), 100*1024); cache->cache_undefined_obj_min_size=temp; - MESA_load_profile_int_def(profile_path, section, "cache_minimum_time_override", &(cache->minimum_cache_seconds), 60*5); cache->gc_evbase=gc_evbase; - cache->default_cache_policy.key_descr.qs_num=0; - cache->default_cache_policy.revalidate=1; + cache->default_cache_policy.cache_key.qs_num=0; + cache->default_cache_policy.no_revalidate=0; cache->default_cache_policy.cache_dyn_url=0; cache->default_cache_policy.cache_cookied_cont=0; cache->default_cache_policy.ignore_req_nocache=0; cache->default_cache_policy.ignore_res_nocache=0; cache->default_cache_policy.force_caching=0; - cache->default_cache_policy.min_use=1; + cache->default_cache_policy.min_use=0; cache->default_cache_policy.pinning_time_sec=0; cache->default_cache_policy.inactive_time_sec=0; - cache->default_cache_policy.max_cache_size_mb=0; - cache->default_cache_policy.max_cache_obj_size_mb=1024;//<1GB by default + cache->default_cache_policy.max_cache_size=0; + cache->default_cache_policy.max_cache_obj_size=1024*1024*1024;//<1GB by default - cache->table_url_constraint=Maat_table_register(feather, "PXY_CACHE_HTTP_URL"); - cache->table_cookie_constraint=Maat_table_register(feather, "PXY_CACHE_HTTP_COOKIE"); - - cache->cache_param_idx=Maat_rule_get_ex_new_index(feather, "PXY_CACHE_COMPILE", - cache_param_new, cache_param_free, 0, cache); - + if(cache->cache_policy_enabled) + { + cache->table_url_constraint=Maat_table_register(feather, "PXY_CACHE_HTTP_URL"); + cache->table_cookie_constraint=Maat_table_register(feather, "PXY_CACHE_HTTP_COOKIE"); + + cache->cache_param_idx=Maat_rule_get_ex_new_index(feather, "PXY_CACHE_COMPILE", + cache_param_new, cache_param_free, cache_param_dup, + 0, cache); + cache->ref_feather=feather; + } cache_stat_init(cache); return cache; error_out: @@ -545,7 +767,6 @@ struct cache_pending_context { enum cache_pending_result status; int is_undefined_obj; - struct request_freshness req_fresshness; char* req_if_none_match, *req_if_modified_since; const struct tfe_http_half * request; @@ -566,7 +787,10 @@ void cache_pending_ctx_free_cb(void* p) if(ctx->f_tango_cache_fetch) { future_destroy(ctx->f_tango_cache_fetch); - } + } + FREE(&(ctx->cached_obj_meta.etag)); + FREE(&(ctx->cached_obj_meta.last_modified)); + FREE(&(ctx->cached_obj_meta.content_type)); free(ctx); return; } @@ -631,50 +855,91 @@ static void cache_query_meta_on_fail(enum e_future_error err, const char * what, } struct cache_mid { + enum cache_pending_result result; + struct request_freshness req_fresshness; + char shall_bypass; + char is_using_exception_param; + char is_dyn_url; + char has_cookie; + char use_cnt; + int cfg_id; + char* cache_key; struct cache_param* param; }; +void cache_mid_clear(struct cache_mid **mid) +{ + if(*mid==NULL) + { + return; + } + if((*mid)->is_using_exception_param) + { + cache_param_free(0, NULL, NULL, (void**)&((*mid)->param), 0, NULL); + } + FREE(&((*mid)->cache_key)); + FREE(mid); + return; +} + +#define CACHE_ACTION_BYPASS 0x80 enum cache_pending_result web_cache_async_pending(struct cache_handle* handle, unsigned int thread_id, const struct tfe_http_half * request, struct cache_mid** mid, struct future* f_revalidate) { - struct request_freshness req_fresshness={0,0}; enum cache_pending_result result=PENDING_RESULT_FOBIDDEN; int is_undefined_obj=0; struct Maat_rule_t cache_policy; - const struct cache_param* param=&(handle->default_cache_policy); + struct cache_param* param=&(handle->default_cache_policy); MAAT_RULE_EX_DATA ex_data=NULL; scan_status_t scan_mid=NULL; int ret=0; const char* cookie=NULL; - ret=Maat_full_scan_string(handle->ref_feather, handle->table_url_constraint, CHARSET_UTF8, - request->req_spec.url, strlen(request->req_spec.url), - &cache_policy, NULL, 1, &scan_mid, thread_id); - + struct cache_mid* _mid=ALLOC(struct cache_mid, 1); + *mid=_mid; cookie=tfe_http_std_field_read(request, TFE_HTTP_COOKIE); - if(cookie && ret<=0) + if(cookie) { - ret=Maat_full_scan_string(handle->ref_feather, handle->table_cookie_constraint, CHARSET_UTF8, - cookie, strlen(cookie), + _mid->has_cookie=1; + } + _mid->is_dyn_url=is_dynamic_url(request->req_spec.url); + if(handle->cache_policy_enabled) + { + ret=Maat_full_scan_string(handle->ref_feather, handle->table_url_constraint, CHARSET_UTF8, + request->req_spec.url, strlen(request->req_spec.url), &cache_policy, NULL, 1, &scan_mid, thread_id); - } - Maat_clean_status(&scan_mid); + - if(ret>0) - { - ex_data=Maat_rule_get_ex_data(handle->ref_feather, &cache_policy, handle->cache_param_idx); - if(ex_data!=NULL) + if(cookie && ret<=0) { - param=(const struct cache_param*)ex_data; + ret=Maat_full_scan_string(handle->ref_feather, handle->table_cookie_constraint, CHARSET_UTF8, + cookie, strlen(cookie), + &cache_policy, NULL, 1, &scan_mid, thread_id); + } + Maat_clean_status(&scan_mid); + + if(ret>0) + { + ex_data=Maat_rule_get_ex_data(handle->ref_feather, &cache_policy, handle->cache_param_idx); + if(ex_data!=NULL) + { + param=(struct cache_param*)ex_data; + _mid->is_using_exception_param=1; + _mid->param=param; + } + if(cache_policy.action==CACHE_ACTION_BYPASS) + { + _mid->shall_bypass=1; + } + _mid->cfg_id=cache_policy.config_id; + } + if(_mid->shall_bypass || + (!param->cache_dyn_url && _mid->is_dyn_url && param->cache_key.qs_num==0) || + (param->cache_cookied_cont && _mid->has_cookie) ) + { + _mid->result=PENDING_RESULT_FOBIDDEN; + return _mid->result; } } - if(param->cache_dyn_url==0 && is_dynamic_url(request->req_spec.url)) - { - return FORBIDDEN; - } - if(param->cache_cookied_cont==0 && cookie!=NULL) - { - return FORBIDDEN; - } - enum cache_pending_action get_action=tfe_cache_get_pending(request, &(req_fresshness)); + enum cache_pending_action get_action=tfe_cache_get_pending(request, &(_mid->req_fresshness)); switch(get_action) { case UNDEFINED: @@ -705,51 +970,68 @@ enum cache_pending_result web_cache_async_pending(struct cache_handle* handle, u result=PENDING_RESULT_ALLOWED; break; default: - result=PENDING_RESULT_REVALIDATE; + if(param->no_revalidate) + { + result=PENDING_RESULT_ALLOWED; + } + else + { + result=PENDING_RESULT_REVALIDATE; + } break; } if(result!=PENDING_RESULT_REVALIDATE) { - return result; + _mid->result=result; + return _mid->result; } struct tango_cache_meta_get meta; memset(&meta, 0, sizeof(meta)); - meta.url=request->req_spec.url; - meta.get=req_fresshness; + if(param->cache_key.is_not_empty) + { + _mid->cache_key=get_cache_key(request, &(param->cache_key)); + meta.url = _mid->cache_key; + } + else + { + meta.url = request->req_spec.url; + } + meta.get = _mid->req_fresshness; struct promise* p=future_to_promise(f_revalidate); struct cache_pending_context* ctx=ALLOC(struct cache_pending_context, 1); ctx->status=PENDING_RESULT_FOBIDDEN; ctx->ref_handle=handle; - ctx->req_fresshness=req_fresshness; ctx->url=tfe_strdup(request->req_spec.url); + ctx->req_if_modified_since=tfe_strdup(tfe_http_std_field_read(request, TFE_HTTP_IF_MODIFIED_SINCE)); ctx->req_if_none_match=tfe_strdup(tfe_http_std_field_read(request, TFE_HTTP_IF_NONE_MATCH)); promise_set_ctx(p, ctx, cache_pending_ctx_free_cb); ATOMIC_INC(&(handle->stat_val[STAT_CACHE_PENDING])); ctx->f_tango_cache_fetch=future_create("_cache_pend", cache_query_meta_on_succ, cache_query_meta_on_fail, p); - int ret=tango_cache_head_object(handle->clients[thread_id], ctx->f_tango_cache_fetch, &meta); + ret=tango_cache_head_object(handle->clients[thread_id], ctx->f_tango_cache_fetch, &meta); if(ret<0) { cache_pending_ctx_free_cb(ctx); - return PENDING_RESULT_FOBIDDEN; + _mid->result=PENDING_RESULT_FOBIDDEN; + return _mid->result; } - assert(ret==0); - return PENDING_RESULT_REVALIDATE; + _mid->result=PENDING_RESULT_REVALIDATE; + + return _mid->result; } int web_cache_async_query(struct cache_handle* handle, unsigned int thread_id, - const struct tfe_http_half * request, struct future* f) + const struct tfe_http_half * request, struct cache_mid** mid, struct future* f) { struct request_freshness req_fresshness; enum cache_pending_action get_action; struct cache_query_context* query_ctx=NULL; struct promise* p=NULL; struct future* _f=NULL; - - get_action=tfe_cache_get_pending(request, &req_fresshness); - assert(get_action!=FORBIDDEN); + struct cache_mid* _mid=*mid; + assert(_mid->result!=PENDING_RESULT_FOBIDDEN); if(ATOMIC_READ(&(handle->stat_val[STAT_CACHE_QUERYING])) > ATOMIC_READ(&(handle->put_concurrency_max))) { @@ -760,6 +1042,7 @@ int web_cache_async_query(struct cache_handle* handle, unsigned int thread_id, struct tango_cache_meta_get meta; memset(&meta, 0, sizeof(meta)); meta.url=request->req_spec.url; + meta.get=_mid->req_fresshness; memcpy(&(meta.get), &req_fresshness, sizeof(meta.get)); query_ctx=ALLOC(struct cache_query_context, 1); query_ctx->ref_handle=handle; @@ -805,17 +1088,28 @@ static void wrap_cache_update_on_fail(enum e_future_error err, const char * what } struct cache_update_context* web_cache_update_start(struct cache_handle* handle, unsigned int thread_id, - const struct tfe_http_session * session) + const struct tfe_http_session * session, struct cache_mid **mid) { struct cache_update_context* update_ctx=NULL; struct response_freshness resp_freshness; enum cache_pending_action put_action; struct tango_cache_ctx *write_ctx=NULL; - char cont_len_str[TFE_STRING_MAX]={0}, user_tag_str[TFE_STRING_MAX]={0}; + char cont_type_str[TFE_STRING_MAX]={0}, user_tag_str[TFE_STRING_MAX]={0}; const char* value=NULL; char *tmp=NULL; int i=0, is_undefined_obj=0; size_t content_len=0; + const struct cache_param* param=NULL; + struct cache_mid* _mid=*mid; + + if(_mid!=NULL && _mid->is_using_exception_param) + { + param=_mid->param; + } + else + { + param=&(handle->default_cache_policy); + } if(session->resp->resp_spec.content_length) { sscanf(session->resp->resp_spec.content_length, "%lu", &content_len); @@ -824,19 +1118,24 @@ struct cache_update_context* web_cache_update_start(struct cache_handle* handle, put_action=tfe_cache_put_pending(session->resp, &resp_freshness); switch(put_action){ case FORBIDDEN: - ATOMIC_INC(&(handle->stat_val[STAT_CACHE_UPLOAD_FORBIDEN])); - TFE_LOG_DEBUG(handle->logger, "cache update forbiden: %s", session->req->req_spec.url); - return NULL; - case REVALIDATE: - case ALLOWED: - break; - case UNDEFINED: - if(handle->cache_undefined_obj_enabled && content_lencache_undefined_obj_min_size) + if(!(param->ignore_res_nocache || param->force_caching)) { + ATOMIC_INC(&(handle->stat_val[STAT_CACHE_UPLOAD_FORBIDEN])); TFE_LOG_DEBUG(handle->logger, "cache update forbiden: %s", session->req->req_spec.url); + return NULL; + } + break; + case REVALIDATE: + case ALLOWED: + case UNDEFINED: + if(_mid->shall_bypass + || content_len > param->max_cache_obj_size + || (!param->cache_cookied_cont && _mid->has_cookie) ) + { + ATOMIC_INC(&(handle->stat_val[STAT_CACHE_UPLOAD_BYPASS])); + TFE_LOG_DEBUG(handle->logger, "cache update bypass: %d : %s", _mid->cfg_id, session->req->req_spec.url); return NULL; } - is_undefined_obj=1; break; default: assert(0); @@ -847,15 +1146,37 @@ struct cache_update_context* web_cache_update_start(struct cache_handle* handle, ATOMIC_INC(&(handle->stat_val[STAT_CACHE_UPLOAD_ABANDON])); return NULL; } + const char* key=NULL; + size_t key_len=0; + if(param->min_use>0) + { + if(_mid->cache_key) + { + key=_mid->cache_key; + key_len=strlen(_mid->cache_key); + } + else + { + key=session->req->req_spec.url; + key_len=strlen(session->req->req_spec.url); + } + _mid->use_cnt=counting_bloom_check(handle->cache_key_bloom[thread_id], key, key_len); + + if(_mid->use_cntmin_use) + { + counting_bloom_add(handle->cache_key_bloom[thread_id], key, key_len); + return NULL; + } + } ATOMIC_INC(&(handle->stat_val[STAT_CACHE_UPLOADING])); struct tango_cache_meta_put meta; memset(&meta, 0, sizeof(meta)); meta.url=session->req->req_spec.url; - i=0; + i=0; - snprintf(cont_len_str, sizeof(cont_len_str), "content-type:%s",session->resp->resp_spec.content_type); - meta.std_hdr[i]=cont_len_str; + snprintf(cont_type_str, sizeof(cont_type_str), "content-type:%s",session->resp->resp_spec.content_type); + meta.std_hdr[i]=cont_type_str; i++; const char* etag=tfe_http_std_field_read(session->resp, TFE_HTTP_ETAG); const char* last_modified=tfe_http_std_field_read(session->resp, TFE_HTTP_LAST_MODIFIED); @@ -868,7 +1189,8 @@ struct cache_update_context* web_cache_update_start(struct cache_handle* handle, meta.usertag_len=strlen(user_tag_str)+1; } meta.put=resp_freshness; - + meta.put.timeout=MAX(param->pinning_time_sec, resp_freshness.timeout); + struct wrap_cache_put_ctx* _cache_put_ctx=ALLOC(struct wrap_cache_put_ctx, 1); _cache_put_ctx->url=tfe_strdup(session->req->req_spec.url); _cache_put_ctx->start=time(NULL); diff --git a/plugin/business/pangu-http/src/pangu_web_cache.h b/plugin/business/pangu-http/src/pangu_web_cache.h index db0a234..0f81852 100644 --- a/plugin/business/pangu-http/src/pangu_web_cache.h +++ b/plugin/business/pangu-http/src/pangu_web_cache.h @@ -2,6 +2,7 @@ #include #include #include +#include enum cache_query_status { @@ -13,7 +14,9 @@ enum cache_query_status WEB_CACHE_HIT }; struct cache_handle; -struct cache_handle* create_web_cache_handle(const char* profile_path, const char* section, struct event_base* gc_evbase, void *logger); +struct cache_handle* create_web_cache_handle(const char* profile_path, const char* section, + struct event_base* gc_evbase, Maat_feather_t feather, void *logger); + struct cached_meta { size_t content_length; @@ -25,7 +28,7 @@ const struct cached_meta* cache_query_result_read_meta(future_result_t * result) size_t cache_query_result_get_data(future_result_t * result, const unsigned char** pp_data); int web_cache_async_query(struct cache_handle* handle, unsigned int thread_id, - const struct tfe_http_half * request, struct future* f); + const struct tfe_http_half * request, struct cache_mid** mid, struct future* f); enum cache_query_result_type @@ -51,8 +54,9 @@ struct cache_mid; const struct cached_meta* cache_pending_result_read_meta(future_result_t * result); enum cache_pending_result web_cache_async_pending(struct cache_handle* handle, unsigned int thread_id, - const struct tfe_http_half * request, struct cache_mid **mid, struct future* f_revalidate); -void cache_mid_free(struct cache_mid **mid); + const struct tfe_http_half * request, struct cache_mid** mid, struct future* f_revalidate); + +void cache_mid_clear(struct cache_mid **mid); diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index 970d191..cd9923c 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -248,6 +248,7 @@ add_dependencies(libcurl-static libcurl) set_property(TARGET libcurl-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libcurl.a) set_property(TARGET libcurl-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) +### hiredis ExternalProject_Add(hiredis PREFIX hiredis URL ${CMAKE_CURRENT_SOURCE_DIR}/hiredis-0.14.0.zip URL_MD5 376af92277701fae52a8c917c3ce3044 @@ -264,3 +265,20 @@ add_library(hiredis-static STATIC IMPORTED GLOBAL) add_dependencies(libcurl-static hiredis) set_property(TARGET hiredis-static PROPERTY IMPORTED_LOCATION ${SOURCE_DIR}/libhiredis.a) set_property(TARGET hiredis-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${HIREDIS_INCLUDE_DIRECTORIES}) + +### dablooms +ExternalProject_Add(dablooms PREFIX dablooms + URL ${CMAKE_CURRENT_SOURCE_DIR}/dablooms-0.9.1.tar.gz + URL_MD5 0c725d3066d279299438fc9b00d492a5 + CONFIGURE_COMMAND "" + BUILD_COMMAND make + INSTALL_COMMAND make install prefix= + BUILD_IN_SOURCE 1) + +ExternalProject_Get_Property(dablooms INSTALL_DIR) +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) + +add_library(libdablooms SHARED IMPORTED GLOBAL) +add_dependencies(libdablooms dablooms) +set_property(TARGET libdablooms PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libdablooms.a) +set_property(TARGET libdablooms PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) diff --git a/vendor/dablooms-0.9.1.tar.gz b/vendor/dablooms-0.9.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f24fefb2bf363408b8e022f0c3c14e4955e1fbe9 GIT binary patch literal 25960 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 b=Fj|@Kl5k)%%A!57|;I?-Cl&o00;sAFZVqr literal 0 HcmV?d00001