Add url_rewrite_extras and store_id_extras for redirector and store_id helpers The url_rewrite_extras/store_id_extras is a "quoted string" with logformat %macro support. It is used to modify the request line for redirector and storeId helpers. The url rewrite and store_id helpers request format now is: url [ extras] and the default value for extras is: "%>a/%>A %un %>rm myip=%la myport=%lp" Example usage: url_rewrite_extras "Note1=%{Note1}note Note2=%{Note2}note" This is a Measurement Factory project. === modified file 'src/SquidConfig.h' --- src/SquidConfig.h 2014-01-12 17:51:12 +0000 +++ src/SquidConfig.h 2014-02-24 15:57:49 +0000 @@ -523,40 +523,44 @@ char *flags; acl_access *cert_error; SSL_CTX *sslContext; sslproxy_cert_sign *cert_sign; sslproxy_cert_adapt *cert_adapt; } ssl_client; #endif char *accept_filter; int umask; int max_filedescriptors; int workers; CpuAffinityMap *cpuAffinityMap; #if USE_LOADABLE_MODULES wordlist *loadable_module_names; #endif int client_ip_max_connections; + char *redirector_extras; + + char *storeId_extras; + struct { int v4_first; ///< Place IPv4 first in the order of DNS results. ssize_t packet_max; ///< maximum size EDNS advertised for DNS replies. } dns; }; extern SquidConfig Config; class SquidConfig2 { public: struct { int enable_purge; int mangle_request_headers; } onoff; uid_t effectiveUserID; gid_t effectiveGroupID; }; === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2014-02-10 09:59:19 +0000 +++ src/cache_cf.cc 2014-02-23 20:44:06 +0000 @@ -3020,40 +3020,55 @@ if (!token) { self_destruct(); return; } while (*token && xisspace(*token)) ++token; if (!*token) { self_destruct(); return; } *var = xstrdup((char *) token); } #define dump_eol dump_string #define free_eol free_string static void +parse_TokenOrQuotedString(char **var) +{ + char *token = ConfigParser::NextQuotedToken(); + safe_free(*var); + + if (token == NULL) + self_destruct(); + + *var = xstrdup(token); +} + +#define dump_TokenOrQuotedString dump_string +#define free_TokenOrQuotedString free_string + +static void dump_time_t(StoreEntry * entry, const char *name, time_t var) { storeAppendPrintf(entry, "%s %d seconds\n", name, (int) var); } void parse_time_t(time_t * var) { time_msec_t tval; parseTimeLine(&tval, T_SECOND_STR, false); *var = static_cast(tval/1000); } static void free_time_t(time_t * var) { *var = 0; } static void === modified file 'src/cf.data.depend' --- src/cf.data.depend 2013-08-29 09:21:53 +0000 +++ src/cf.data.depend 2014-02-23 20:44:21 +0000 @@ -38,39 +38,40 @@ adaptation_service_chain_type icap_service ecap_service icap_access_type icap_class acl icap_class_type icap_service icap_service_type icap_service_failure_limit ecap_service_type int kb_int64_t kb_size_t logformat YesNoNone memcachemode note acl obsolete onoff peer peer_access cache_peer acl pipelinePrefetch PortCfg QosConfig +TokenOrQuotedString refreshpattern removalpolicy size_t IpAddress_list string string time_msec time_t tristate uri_whitespace u_short wccp2_method wccp2_amethod wccp2_service wccp2_service_info wordlist sslproxy_ssl_bump acl sslproxy_cert_sign acl sslproxy_cert_adapt acl === modified file 'src/cf.data.pre' --- src/cf.data.pre 2014-01-30 21:24:44 +0000 +++ src/cf.data.pre 2014-02-24 16:32:19 +0000 @@ -4590,41 +4590,41 @@ Enables turning ICMP pinger on and off with a simple squid -k reconfigure. DOC_END COMMENT_START OPTIONS FOR URL REWRITING ----------------------------------------------------------------------------- COMMENT_END NAME: url_rewrite_program redirect_program TYPE: wordlist LOC: Config.Program.redirect DEFAULT: none DOC_START Specify the location of the executable URL rewriter to use. Since they can perform almost any function there isn't one included. For each requested URL, the rewriter will receive on line with the format - [channel-ID ] URL client_ip "/" fqdn user method [ kv-pairs] + [channel-ID ] URL [ extras] After processing the request the helper must reply using the following format: [channel-ID ] result [ kv-pairs] The result code can be: OK status=30N url="..." Redirect the URL to the one supplied in 'url='. 'status=' is optional and contains the status code to send the client in Squids HTTP response. It must be one of the HTTP redirect status codes: 301, 302, 303, 307, 308. When no status is given Squid will use 302. OK rewrite-url="..." Rewrite the URL to the one supplied in 'rewrite-url='. The new URL is fetched directly by Squid and returned to the client as the response to its request. @@ -4738,93 +4738,117 @@ This clause supports both fast and slow acl types. See http://wiki.squid-cache.org/SquidFaq/SquidAcl for details. DOC_END NAME: url_rewrite_bypass redirector_bypass TYPE: onoff LOC: Config.onoff.redirector_bypass DEFAULT: off DOC_START When this is 'on', a request will not go through the redirector if all the helpers are busy. If this is 'off' and the redirector queue grows too large, Squid will exit with a FATAL error and ask you to increase the number of redirectors. You should only enable this if the redirectors are not critical to your caching system. If you use redirectors for access control, and you enable this option, users may have access to pages they should not be allowed to request. DOC_END +NAME: url_rewrite_extras format +TYPE: TokenOrQuotedString +LOC: Config.redirector_extras +DEFAULT: "%>a/%>A %un %>rm myip=%la myport=%lp" +DOC_START + Specifies a string to be append to request line format for the + rewriter helper. "Quoted" format values may contain spaces and + logformat %macros. In theory, any logformat %macro can be used. + In practice, a %macro expands as a dash (-) if the helper request is + sent before the required macro information is available to Squid. +DOC_END + COMMENT_START OPTIONS FOR STORE ID ----------------------------------------------------------------------------- COMMENT_END NAME: store_id_program storeurl_rewrite_program TYPE: wordlist LOC: Config.Program.store_id DEFAULT: none DOC_START Specify the location of the executable StoreID helper to use. Since they can perform almost any function there isn't one included. For each requested URL, the helper will receive one line with the format - [channel-ID ] URL client_ip "/" fqdn user method [ kv-pairs] + [channel-ID ] URL [ extras] After processing the request the helper must reply using the following format: [channel-ID ] result [ kv-pairs] The result code can be: OK store-id="..." Use the StoreID supplied in 'store-id='. ERR The default is to use HTTP request URL as the store ID. BH An internal error occured in the helper, preventing a result being identified. - Helper programs should be prepared to receive and possibly ignore additional - kv-pairs with keys they do not support. + Helper programs should be prepared to receive and possibly ignore + additional whitespace-separated tokens on each input line. When using the concurrency= option the protocol is changed by introducing a query channel tag in front of the request/response. The query channel tag is a number between 0 and concurrency-1. This value must be echoed back unchanged to Squid as the first part of the response relating to its request. NOTE: when using StoreID refresh_pattern will apply to the StoreID returned from the helper and not the URL. WARNING: Wrong StoreID value returned by a careless helper may result in the wrong cached response returned to the user. By default, a StoreID helper is not used. DOC_END +NAME: store_id_extras format +TYPE: TokenOrQuotedString +LOC: Config.storeId_extras +DEFAULT: "%>a/%>A %un %>rm myip=%la myport=%lp" +DOC_START + Specifies a string to be append to request line format for the + StoreId helper. "Quoted" format values may contain spaces and + logformat %macros. In theory, any logformat %macro can be used. + In practice, a %macro expands as a dash (-) if the helper request is + sent before the required macro information is available to Squid. +DOC_END + NAME: store_id_children storeurl_rewrite_children TYPE: HelperChildConfig DEFAULT: 20 startup=0 idle=1 concurrency=0 LOC: Config.storeIdChildren DOC_START The maximum number of StoreID helper processes to spawn. If you limit it too few Squid will have to wait for them to process a backlog of requests, slowing it down. If you allow too many they will use RAM and other system resources noticably. The startup= and idle= options allow some measure of skew in your tuning. startup= Sets a minimum of how many processes are to be spawned when Squid starts or reconfigures. When set to zero the first request will cause spawning of the first child process to handle it. Starting too few will cause an initial slowdown in traffic as Squid === modified file 'src/cf_gen.cc' --- src/cf_gen.cc 2013-07-21 19:24:35 +0000 +++ src/cf_gen.cc 2014-02-24 16:35:34 +0000 @@ -141,40 +141,41 @@ public: Type(const char *str) : name(str) {} ~Type() {} std::string name; TypeDepList depend; }; typedef std::list TypeList; static const char WS[] = " \t\n"; static int gen_default(const EntryList &, std::ostream &); static void gen_parse(const EntryList &, std::ostream &); static void gen_dump(const EntryList &, std::ostream&); static void gen_free(const EntryList &, std::ostream&); static void gen_conf(const EntryList &, std::ostream&, bool verbose_output); static void gen_default_if_none(const EntryList &, std::ostream&); static void gen_default_postscriptum(const EntryList &, std::ostream&); static bool isDefined(const std::string &name); static const char *available_if(const std::string &name); +static const char *gen_quote_escape(const std::string &var); static void checkDepend(const std::string &directive, const char *name, const TypeList &types, const EntryList &entries) { for (TypeList::const_iterator t = types.begin(); t != types.end(); ++t) { if (t->name.compare(name) != 0) continue; for (TypeDepList::const_iterator dep = t->depend.begin(); dep != t->depend.end(); ++dep) { EntryList::const_iterator entry = entries.begin(); for (; entry != entries.end(); ++entry) { if (entry->name.compare(*dep) == 0) break; } if (entry == entries.end()) { std::cerr << "ERROR: '" << directive << "' (" << name << ") depends on '" << *dep << "'\n"; exit(1); } } return; } @@ -523,82 +524,82 @@ if (!entry->loc.size()) { std::cerr << "NO LOCATION FOR " << entry->name << std::endl; rc |= 1; continue; } if (!entry->defaults.preset.size() && entry->defaults.if_none.empty()) { std::cerr << "NO DEFAULT FOR " << entry->name << std::endl; rc |= 1; continue; } if (!entry->defaults.preset.size() || entry->defaults.preset.front().compare("none") == 0) { fout << " // No default for " << entry->name << std::endl; } else { if (entry->ifdef.size()) fout << "#if " << entry->ifdef << std::endl; for (LineList::const_iterator l = entry->defaults.preset.begin(); l != entry->defaults.preset.end(); ++l) { - fout << " default_line(\"" << entry->name << " " << *l << "\");" << std::endl; + fout << " default_line(\"" << entry->name << " " << gen_quote_escape(*l) << "\");" << std::endl; } if (entry->ifdef.size()) fout << "#endif" << std::endl; } } fout << " cfg_filename = NULL;" << std::endl << "}" << std::endl << std::endl; return rc; } static void gen_default_if_none(const EntryList &head, std::ostream &fout) { fout << "static void" << std::endl << "defaults_if_none(void)" << std::endl << "{" << std::endl << " cfg_filename = \"Default Configuration (if absent)\";" << std::endl << " config_lineno = 0;" << std::endl; for (EntryList::const_iterator entry = head.begin(); entry != head.end(); ++entry) { assert(entry->name.size()); if (!entry->loc.size()) continue; if (entry->defaults.if_none.empty()) continue; if (!entry->defaults.preset.empty()) { std::cerr << "ERROR: " << entry->name << " has preset defaults. DEFAULT_IF_NONE cannot be true." << std::endl; exit(1); } if (entry->ifdef.size()) fout << "#if " << entry->ifdef << std::endl; fout << " if (check_null_" << entry->type << "(" << entry->loc << ")) {" << std::endl; for (LineList::const_iterator l = entry->defaults.if_none.begin(); l != entry->defaults.if_none.end(); ++l) - fout << " default_line(\"" << entry->name << " " << *l <<"\");" << std::endl; + fout << " default_line(\"" << entry->name << " " << gen_quote_escape(*l) <<"\");" << std::endl; fout << " }" << std::endl; if (entry->ifdef.size()) fout << "#endif" << std::endl; } fout << " cfg_filename = NULL;" << std::endl << "}" << std::endl << std::endl; } /// append configuration options specified by POSTSCRIPTUM lines static void gen_default_postscriptum(const EntryList &head, std::ostream &fout) { fout << "static void" << std::endl << "defaults_postscriptum(void)" << std::endl << "{" << std::endl << " cfg_filename = \"Default Configuration (postscriptum)\";" << std::endl << " config_lineno = 0;" << std::endl; @@ -805,62 +806,81 @@ "# " << available_if(entry->ifdef) << std::endl << "#" << std::endl; } enabled = 0; } // Display DOC_START section if (verbose_output && entry->doc.size()) { for (LineList::const_iterator line = entry->doc.begin(); line != entry->doc.end(); ++line) { fout << "#" << *line << std::endl; } } if (entry->defaults.docs.size()) { // Display the DEFAULT_DOC line(s) def = entry->defaults.docs; } else { if (entry->defaults.preset.size() && entry->defaults.preset.front().compare("none") != 0) { // Display DEFAULT: line(s) for (LineList::const_iterator l = entry->defaults.preset.begin(); l != entry->defaults.preset.end(); ++l) { - snprintf(buf, sizeof(buf), "%s %s", entry->name.c_str(), l->c_str()); + snprintf(buf, sizeof(buf), "%s %s", entry->name.c_str(), gen_quote_escape(*l)); def.push_back(buf); } } else if (entry->defaults.if_none.size()) { // Display DEFAULT_IF_NONE: line(s) for (LineList::const_iterator l = entry->defaults.if_none.begin(); l != entry->defaults.if_none.end(); ++l) { - snprintf(buf, sizeof(buf), "%s %s", entry->name.c_str(), l->c_str()); + snprintf(buf, sizeof(buf), "%s %s", entry->name.c_str(), gen_quote_escape(*l)); def.push_back(buf); } } } // Display "none" if no default is set or comments to display if (def.empty() && entry->nocomment.empty() && entry->name.compare("comment") != 0) def.push_back("none"); if (verbose_output && def.size()) { fout << "#Default:\n"; while (def.size()) { fout << "# " << def.front() << std::endl; def.pop_front(); } if (entry->doc.empty() && entry->nocomment.empty()) fout << std::endl; } if (verbose_output && entry->nocomment.size()) fout << "#" << std::endl; if (enabled || verbose_output) { for (LineList::const_iterator line = entry->nocomment.begin(); line != entry->nocomment.end(); ++line) { if (!enabled && line->at(0) != '#') fout << "#"; fout << *line << std::endl; } } if (verbose_output && entry->doc.size()) { fout << std::endl; } } } + +static const char * +gen_quote_escape(const std::string &var) +{ + static std::string esc; + esc.clear(); + + for (int i = 0; i < var.length(); ++i) { + switch (var[i]) { + case '"': + case '\\': + esc += '\\'; + default: + esc += var[i]; + } + } + + return esc.c_str(); +} === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2013-12-06 23:52:26 +0000 +++ src/client_side_request.cc 2014-02-21 17:07:39 +0000 @@ -879,41 +879,41 @@ static void clientRedirectAccessCheckDone(allow_t answer, void *data) { ClientRequestContext *context = (ClientRequestContext *)data; ClientHttpRequest *http = context->http; context->acl_checklist = NULL; if (answer == ACCESS_ALLOWED) redirectStart(http, clientRedirectDoneWrapper, context); else { HelperReply nilReply; nilReply.result = HelperReply::Error; context->clientRedirectDone(nilReply); } } void ClientRequestContext::clientRedirectStart() { debugs(33, 5, HERE << "'" << http->uri << "'"); - + (void)SyncNotes(*http->al, *http->request); if (Config.accessList.redirector) { acl_checklist = clientAclChecklistCreate(Config.accessList.redirector, http); acl_checklist->nonBlockingCheck(clientRedirectAccessCheckDone, this); } else redirectStart(http, clientRedirectDoneWrapper, this); } /** * This methods handles Access checks result of StoreId access list. * Will handle as "ERR" (no change) in a case Access is not allowed. */ static void clientStoreIdAccessCheckDone(allow_t answer, void *data) { ClientRequestContext *context = static_cast(data); ClientHttpRequest *http = context->http; context->acl_checklist = NULL; if (answer == ACCESS_ALLOWED) storeIdStart(http, clientStoreIdDoneWrapper, context); === modified file 'src/redirect.cc' --- src/redirect.cc 2013-11-23 00:58:42 +0000 +++ src/redirect.cc 2014-02-24 16:41:43 +0000 @@ -21,40 +21,41 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" #include "acl/Checklist.h" #include "client_side.h" #include "client_side_reply.h" #include "client_side_request.h" #include "comm/Connection.h" #include "fde.h" #include "fqdncache.h" +#include "format/Format.h" #include "globals.h" #include "HttpRequest.h" #include "mgr/Registration.h" #include "redirect.h" #include "rfc1738.h" #include "SBuf.h" #include "SquidConfig.h" #include "Store.h" #if USE_AUTH #include "auth/UserRequest.h" #endif #if USE_SSL #include "ssl/support.h" #endif /// url maximum lengh + extra informations passed to redirector #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024) class RedirectStateData { @@ -65,40 +66,42 @@ void *data; SBuf orig_url; Ip::Address client_addr; const char *client_ident; const char *method_s; HLPCB *handler; private: CBDATA_CLASS2(RedirectStateData); }; static HLPCB redirectHandleReply; static HLPCB storeIdHandleReply; static helper *redirectors = NULL; static helper *storeIds = NULL; static OBJH redirectStats; static OBJH storeIdStats; static int redirectorBypassed = 0; static int storeIdBypassed = 0; +static Format::Format *redirectorExtrasFmt = NULL; +static Format::Format *storeIdExtrasFmt = NULL; CBDATA_CLASS_INIT(RedirectStateData); RedirectStateData::RedirectStateData(const char *url) : data(NULL), orig_url(url), client_addr(), client_ident(NULL), method_s(NULL), handler(NULL) { } RedirectStateData::~RedirectStateData() { } static void redirectHandleReply(void *data, const HelperReply &reply) { @@ -216,49 +219,47 @@ storeAppendPrintf(sentry, "\nNumber of requests bypassed " "because all redirectors were busy: %d\n", redirectorBypassed); } static void storeIdStats(StoreEntry * sentry) { if (storeIds == NULL) { storeAppendPrintf(sentry, "No StoreId helpers defined\n"); return; } helperStats(sentry, storeIds, "StoreId helper Statistics"); if (Config.onoff.store_id_bypass) storeAppendPrintf(sentry, "\nNumber of requests bypassed " "because all StoreId helpers were busy: %d\n", storeIdBypassed); } static void -constructHelperQuery(const char *name, helper *hlp, HLPCB *replyHandler, ClientHttpRequest * http, HLPCB *handler, void *data) +constructHelperQuery(const char *name, helper *hlp, HLPCB *replyHandler, ClientHttpRequest * http, HLPCB *handler, void *data, Format::Format *requestExtrasFmt) { ConnStateData * conn = http->getConn(); const char *fqdn; char buf[MAX_REDIRECTOR_REQUEST_STRLEN]; int sz; Http::StatusCode status; - char claddr[MAX_IPSTRLEN]; - char myaddr[MAX_IPSTRLEN]; /** TODO: create a standalone method to initialize * the RedirectStateData for all the helpers. */ RedirectStateData *r = new RedirectStateData(http->uri); if (conn != NULL) r->client_addr = conn->log_addr; else r->client_addr.setNoAddr(); r->client_ident = NULL; #if USE_AUTH if (http->request->auth_user_request != NULL) { r->client_ident = http->request->auth_user_request->username(); debugs(61, 5, HERE << "auth-user=" << (r->client_ident?r->client_ident:"NULL")); } #endif if (!r->client_ident && http->request->extacl_user.size() > 0) { r->client_ident = http->request->extacl_user.termedBuf(); debugs(61, 5, HERE << "acl-user=" << (r->client_ident?r->client_ident:"NULL")); @@ -272,48 +273,49 @@ #if USE_SSL if (!r->client_ident && conn != NULL && Comm::IsConnOpen(conn->clientConnection)) { r->client_ident = sslGetUserEmail(fd_table[conn->clientConnection->fd].ssl); debugs(61, 5, HERE << "ssl-user=" << (r->client_ident?r->client_ident:"NULL")); } #endif if (!r->client_ident) r->client_ident = dash_str; r->method_s = RequestMethodStr(http->request->method); r->handler = handler; r->data = cbdataReference(data); if ((fqdn = fqdncache_gethostbyaddr(r->client_addr, 0)) == NULL) fqdn = dash_str; - sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s %s/%s %s %s myip=%s myport=%d\n", + static MemBuf requestExtras; + requestExtras.reset(); + if (requestExtrasFmt) + requestExtrasFmt->assemble(requestExtras, http->al, 0); + + sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s%s%s\n", r->orig_url.c_str(), - r->client_addr.toStr(claddr,MAX_IPSTRLEN), - fqdn, - r->client_ident[0] ? rfc1738_escape(r->client_ident) : dash_str, - r->method_s, - http->request->my_addr.toStr(myaddr,MAX_IPSTRLEN), - http->request->my_addr.port()); + requestExtras.hasContent() ? " " : "", + requestExtras.hasContent() ? requestExtras.content() : ""); if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) { if (sz<=0) { status = Http::scInternalServerError; debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED."); } else { status = Http::scRequestUriTooLarge; debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Request passed to " << name << " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN << "). Request ABORTED."); } clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); Ip::Address tmpnoaddr; tmpnoaddr.setNoAddr(); repContext->setReplyToError(ERR_GATEWAY_FAILURE, status, http->request->method, NULL, http->getConn() != NULL && http->getConn()->clientConnection != NULL ? http->getConn()->clientConnection->remote : tmpnoaddr, http->request, @@ -336,116 +338,131 @@ /**** PUBLIC FUNCTIONS ****/ void redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data) { assert(http); assert(handler); debugs(61, 5, "redirectStart: '" << http->uri << "'"); if (Config.onoff.redirector_bypass && redirectors->stats.queue_size) { /* Skip redirector if there is one request queued */ ++redirectorBypassed; HelperReply bypassReply; bypassReply.result = HelperReply::Okay; bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed."); handler(data, bypassReply); return; } - constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data); + constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt); } /** * Handles the StoreID feature helper starting. * For now it cannot be done using the redirectStart method. */ void storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data) { assert(http); assert(handler); debugs(61, 5, "storeIdStart: '" << http->uri << "'"); if (Config.onoff.store_id_bypass && storeIds->stats.queue_size) { /* Skip StoreID Helper if there is one request queued */ ++storeIdBypassed; HelperReply bypassReply; bypassReply.result = HelperReply::Okay; bypassReply.notes.add("message","StoreId helper queue too long. Bypassed."); handler(data, bypassReply); return; } - constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data); + constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt); } void redirectInit(void) { static bool init = false; if (!init) { Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1); Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1); } if (Config.Program.redirect) { if (redirectors == NULL) redirectors = new helper("redirector"); redirectors->cmdline = Config.Program.redirect; redirectors->childs.updateLimits(Config.redirectChildren); redirectors->ipc_type = IPC_STREAM; helperOpenServers(redirectors); } if (Config.Program.store_id) { if (storeIds == NULL) storeIds = new helper("store_id"); storeIds->cmdline = Config.Program.store_id; storeIds->childs.updateLimits(Config.storeIdChildren); storeIds->ipc_type = IPC_STREAM; helperOpenServers(storeIds); } + if (Config.redirector_extras) { + redirectorExtrasFmt = new ::Format::Format("redirecor_extras"); + (void)redirectorExtrasFmt->parse(Config.redirector_extras); + } + + if (Config.storeId_extras) { + storeIdExtrasFmt = new ::Format::Format("storeId_extras"); + (void)storeIdExtrasFmt->parse(Config.storeId_extras); + } + init = true; } void redirectShutdown(void) { /** FIXME: Temporary unified helpers Shutdown * When and if needed for more helpers a separated shutdown * method will be added for each of them. */ if (!storeIds && !redirectors) return; if (redirectors) helperShutdown(redirectors); if (storeIds) helperShutdown(storeIds); if (!shutting_down) return; delete redirectors; redirectors = NULL; delete storeIds; storeIds = NULL; + delete redirectorExtrasFmt; + redirectorExtrasFmt = NULL; + + delete storeIdExtrasFmt; + storeIdExtrasFmt = NULL; }