/* Copyright 2000-2006 The Apache Software Foundation or its licensors, as * applicable. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* This mod-cache-requester modules starts background threads which keeps monitoring soon to expire pages from cache. and if such pages are 'popular' then those pages are re-requested using libcurl so that such pages are not removed from the cache. */ #define CORE_PRIVATE #include "mod_cache_requester.h" #include #if APR_HAVE_UNISTD_H #include #endif #if !APR_HAS_THREADS #error This module does not currently compile unless you have a thread-capable APR. Sorry! #endif # define DEFAULT_NUMBER_OF_THREADS 2 # define DEFAULT_QUEUE_SIZE 10 # define DEFAULT_EXPIRING_SIGNAL_TIME_IN_SECONDS 1000 # define DEFAULT_SECRET_CODE "SECRET_CODE" module AP_MODULE_DECLARE_DATA cache_requester_module; static cache_req_conf_t *sconf; static cache_requester *cache_req_obj; /** * Priority queue management functions */ static long cache_req_get_priority(void *data) { cache_req_expiring_page *expiring_page = (cache_req_expiring_page *) data; return expiring_page->priority; } static void cache_req_set_pos(void *a, apr_ssize_t pos) { cache_req_expiring_page *obj = (cache_req_expiring_page *)a; apr_atomic_set32(&(obj->pos), pos); } static apr_ssize_t cache_req_get_pos(void *a) { cache_req_expiring_page *obj = (cache_req_expiring_page *)a; return apr_atomic_read32(&(obj->pos)); } /** * Queue Management operations. all those operations must be thread safe. use cache_req_obj->lock to ensure mutual exclusion. */ static void create_expiring_page(cache_req_expiring_page **node, request_rec *r) { char *request_uri; *node = (cache_req_expiring_page *) malloc(sizeof(cache_req_expiring_page)); if(!node) { return; } (*node)->priority = 0; request_uri = apr_pstrcat(r->pool, r->server->server_hostname, ":", apr_itoa(r->pool, r->server->port), r->uri, NULL); (*node)->request_uri = malloc(strlen(request_uri) + 1); strcpy((*node)->request_uri, request_uri); (*node)->uri = malloc(strlen(r->uri) + 1); strcpy((*node)->uri, r->uri); (*node)->next = NULL; } static void delete_expiring_page(cache_req_expiring_page *node) { if(node->request_uri) { free(node->request_uri); } if(node->uri) { free(node->uri); } if(node) { free(node); } } static cache_req_expiring_page *insert_into_queue(request_rec *r) { cache_req_expiring_page *value; cache_req_expiring_page *new_value; int queue_size; queue_size = cache_pq_size(cache_req_obj->cache_request_pq); if(queue_size >= sconf->queue_size) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, "cache_requester: cache size queue exceeded"); return NULL; } /*check if the entry for this page already exists or not.*/ value = apr_hash_get(cache_req_obj->expiring_page_hash, r->uri, APR_HASH_KEY_STRING); /* if not, then insert it into the queue, initialize the priority*/ if(value == NULL) { create_expiring_page(&new_value, r); apr_hash_set(cache_req_obj->expiring_page_hash, new_value->uri, APR_HASH_KEY_STRING, new_value); cache_pq_insert(cache_req_obj->cache_request_pq, new_value); // if this is the first entry added in the queue, then lets wake up one of the threads. if(queue_size == 0) { apr_thread_cond_signal(cache_req_obj->cond); } return new_value; } /* if its already there, then increment the priority by one.*/ else { value->priority++; cache_pq_change_priority(cache_req_obj->cache_request_pq, (value->priority - 1), value->priority, value); return value; } } /* delete the entry from the queue. */ static int delete_from_queue(cache_req_expiring_page *expiring_page) { cache_req_expiring_page *value; if(!expiring_page || !(expiring_page->uri)) { return DECLINED; } value = apr_hash_get(cache_req_obj->expiring_page_hash, expiring_page->uri, APR_HASH_KEY_STRING); if(value == NULL) { return DECLINED; } else { apr_hash_set(cache_req_obj->expiring_page_hash, expiring_page->uri, APR_HASH_KEY_STRING, NULL); delete_expiring_page(value); return OK; } } /* * insert the soon-to-expire page into the queue. */ int notify_cache_requester(request_rec *r, cache_handle_t *handle) { const char *is_from_cache_requester; cache_req_expiring_page *value; is_from_cache_requester = apr_table_get(r->headers_in, "Cache-Requester"); if(is_from_cache_requester) { /* verify the request is actually made by mod-cache-requester. */ if(!strcmp(is_from_cache_requester, sconf->secret_code)) { return DECLINED; } return OK; } /* See the expiry time of the page. If its soon to expire, then notify add this page in the queue (if already there then increment the counter) to re-request */ if(!is_soon_to_expire(handle)) { apr_thread_mutex_lock(cache_req_obj->lock); value = insert_into_queue(r); apr_thread_mutex_unlock(cache_req_obj->lock); } return OK; } /* * Checks whether this page is soon-to-expire. returns 0 if its the case */ int is_soon_to_expire(cache_handle_t *handle) { cache_info *info; if(!handle) { return -1; } info = &(handle->cache_obj->info); if((apr_time_sec(info->expire) - apr_time_sec(apr_time_now())) < sconf->expiring_signal_time_in_seconds) { return 0; } return -1; } /* * Process the most popular page, remove it from the queue (in request_page) * - re request it using libcurl - make_curl_request */ static size_t process_received_data(void *buffer, size_t size, size_t nmemb, void *userp); void make_curl_request(char *request_uri, char *secret_code_header) { CURL *curl; struct curl_slist *headers=NULL; int returnValue; curl = curl_easy_init(); headers = curl_slist_append(headers, secret_code_header); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, request_uri); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, process_received_data); returnValue = curl_easy_perform(curl); ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, "cache_requester: URI: %s re-requested, with return value: %d", request_uri, returnValue); } void *APR_THREAD_FUNC request_page(apr_thread_t *thd, void * dummy) { cache_req_expiring_page *curr_obj; request_rec *r; request_rec *rnew; char *secret_code_header; secret_code_header = (char *) malloc (sizeof("Cache-Requester: ") + sizeof(sconf->secret_code) + 1); strcpy(secret_code_header, "Cache-Requester: "); strcat(secret_code_header, sconf->secret_code); for(;;) { apr_thread_mutex_lock(cache_req_obj->lock); /* If no entry in the queue => sleep */ while(cache_pq_size(cache_req_obj->cache_request_pq) == 0) { apr_thread_cond_wait(cache_req_obj->cond, cache_req_obj->lock); } /*Pop the most popular page from the queue*/ curr_obj = cache_pq_pop(cache_req_obj->cache_request_pq); if(!curr_obj) { apr_thread_mutex_unlock(cache_req_obj->lock); continue; } /*Remove entry from the expiring_page and insert it into expiring_page hashmap.*/ apr_hash_set(cache_req_obj->expiring_page_hash, curr_obj->uri, APR_HASH_KEY_STRING, NULL); apr_thread_mutex_unlock(cache_req_obj->lock); //Re-request the page. make_curl_request(curr_obj->request_uri, secret_code_header); //Delete the object. delete_expiring_page(curr_obj); } free(secret_code_header); return NULL; } static size_t process_received_data(void *buffer, size_t size, size_t nmemb, void *userp) { /* * throw away data received as a part of curl request. */ } /** * Directive Configurations. */ static void *create_cache_requester_config(apr_pool_t *p, server_rec *s) { sconf = apr_pcalloc(p, sizeof(cache_req_conf_t)); sconf->number_of_threads = DEFAULT_NUMBER_OF_THREADS; sconf->queue_size = DEFAULT_QUEUE_SIZE; sconf->expiring_signal_time_in_seconds = DEFAULT_EXPIRING_SIGNAL_TIME_IN_SECONDS; sconf->secret_code = DEFAULT_SECRET_CODE; return sconf; } static const char *set_no_of_threads(cmd_parms *parms, void *in_struct_ptr, const char *arg) { apr_size_t val; if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { return "CacheRequesterThreads argument must be an integer representing number of therads."; } if(val < 1) { return "CacheRequesterThreads argument must not be less than 1."; } sconf->number_of_threads = val; return NULL; } static const char *set_max_queue_size(cmd_parms *parms, void *in_struct_ptr, const char *arg) { apr_size_t val; if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { return "CacheRequesterMaxQueueSize argument must be an integer representing number of therads."; } if(val < 1) { return "CacheRequesterMaxQueueSize argument must be greater than 1."; } sconf->queue_size = val; return NULL; } static const char *set_soon_to_expire_time(cmd_parms *parms, void *in_struct_ptr, const char *arg) { apr_size_t val; if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { return "CacheRequesterSoonToExpireTime value must be a positive integer"; } if(val < 1) { return "CacheRequesterSoonToExpireTime argument must be greater than 1."; } sconf->expiring_signal_time_in_seconds = val; return NULL; } static const char *set_secret_code(cmd_parms *parms, void *name, const char *arg) { sconf->secret_code = arg; return NULL; } static const command_rec cache_req_cmds[] = { AP_INIT_TAKE1("CacheRequesterThreads", set_no_of_threads, NULL, RSRC_CONF, "Number of cache requester threads"), AP_INIT_TAKE1("CacheRequesterMaxQueueSize", set_max_queue_size, NULL, RSRC_CONF, "The maximum number of objects allowed to be placed in the cache requester queue."), AP_INIT_TAKE1("CacheRequesterSoonToExpireTime", set_soon_to_expire_time, NULL, RSRC_CONF, "The required time remaining to expire a page to be able to be re-requested (in seconds)"), AP_INIT_TAKE1("CacheRequesterSecretCode", set_secret_code, NULL, RSRC_CONF, "the secret code to authenticate requests made by mod-cache-requester"), {NULL} }; /** * initialize and start the threads. */ static int init_cache_req_obj_threads(apr_pool_t *p, server_rec *s) { int i; cache_req_thread *current_thread; current_thread = cache_req_obj->thread_pool->queue; apr_thread_create(&(current_thread->handle), NULL, request_page, NULL, p); current_thread->next = NULL; for(i=0; i < sconf->number_of_threads-1; i++) { current_thread->next = (cache_req_thread *) apr_palloc(p, sizeof(cache_req_thread)); apr_thread_create(&(current_thread->next->handle), NULL, request_page, NULL, p); current_thread->next->next = NULL; current_thread = current_thread->next; } } /** * module configuration stuff. */ static int cache_requester_child_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { //Here Threads would be instantiated. init_cache_req_obj_threads(p, s); } static int cache_requester_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { // Initialize the curl lib. curl_global_init(CURL_GLOBAL_ALL); //Create main object first. cache_req_obj = (cache_requester *) apr_palloc(p, sizeof(cache_requester)); // instantiate the thread pool. cache_req_obj->thread_pool = (cache_req_thread_pool *) apr_palloc(p, sizeof(cache_req_thread_pool)); // create the lock, condition variable and priority queue. apr_thread_mutex_create(&(cache_req_obj->lock), APR_THREAD_MUTEX_DEFAULT, p); apr_thread_cond_create(&(cache_req_obj->cond), p); cache_req_obj->cache_request_pq = cache_pq_init(sconf->queue_size, cache_req_get_priority, cache_req_get_pos, cache_req_set_pos); // instantiate the hashmaps. cache_req_obj->expiring_page_hash = apr_hash_make(p); //Instantiate the thread queue. cache_req_obj->thread_pool->queue = (cache_req_thread *) apr_palloc(p, sizeof(cache_req_thread)); return OK; } static const cache_req_provider cache_requester_provider = { ¬ify_cache_requester, }; static void register_hooks(apr_pool_t *p) { ap_hook_post_config(cache_requester_post_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(cache_requester_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_register_provider(p, CACHE_REQUESTER_PROVIDER_GROUP, "cache_req", "0", &cache_requester_provider); } module AP_MODULE_DECLARE_DATA cache_requester_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-directory config structure */ NULL, /* merge per-directory config structures */ create_cache_requester_config, /* create per-server config structure */ NULL, /* merge per-server config structures */ cache_req_cmds, /* command apr_table_t */ register_hooks };