Example: Query Remap Plugin¶
The sample remap plugin, query_remap.cc
, maps client requests to a
number of servers based on a hash of the request’s URL query parameter.
This can be useful for spreading load for a given type of request among
backend servers, while still maintaining “stickiness” to a single server
for similar requests. For example, a search engine may want to send
repeated queries for the same keywords to a server that has likely
cached the result from a prior query.
Configuration of query_remap¶
The query remap plugin will allow the query parameter name to be
specified, along with the hostnames of the servers to hash across.
Sample remap.config
rules using query_remap
will look like:
map http://www.example.com/search http://srch1.example.com/search @plugin=query_remap.so @pparam=q @pparam=srch1.example.com @pparam=srch2.example.com @pparam=srch3.example.com
map http://www.example.com/profiles http://prof1.example.com/profiles @plugin=query_remap.so @pparam=user_id @pparam=prof1.example.com @pparam=prof2.example.com
The first @pparam
specifies the query param key for which the value
will be hashed. The remaining parameters list the hostnames of the
servers. A request for http://www.example.com/search?q=apache
will
match the first rule. The plugin will look for the ``q`` parameter and
hash the value ‘apache
‘ to pick from among
srch_[1-3]_.example.com
to send the request.
If the request does not include a ``q`` query parameter and the plugin
decides not to modify the request, the default toURL
‘http://srch1.example.com/search
‘ will be used by TS.
The parameters are passed to the plugin’s tsremap_new_instance
function. In query_remap
, tsremap_new_instance
creates a
plugin-defined query_remap_info
struct to store its configuration
parameters.
}
/* function prototypes */
uint32_t hash_fnv32(char *buf, size_t len);
struct query_remap_info {
The ihandle
, an opaque pointer that can be used to pass
per-instance data, is set to this struct pointer and will be passed to
the TSRemapDoRemap
function when it is triggered for a request.
Dbg(dbg_ctl, "remap plugin initialized");
return TS_SUCCESS;
}
TSReturnCode
TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf ATS_UNUSED, int errbuf_size ATS_UNUSED)
{
/* Called for each remap rule using this plugin. The parameters are parsed here */
int i;
Dbg(dbg_ctl, "new instance fromURL: %s toURL: %s", argv[0], argv[1]);
if (argc < 4) {
TSError("[%s] Missing parameters", PLUGIN_NAME);
return TS_ERROR;
}
/* initialize the struct to store info about this remap instance
the argv parameters are:
0: fromURL
1: toURL
2: query param to hash
3,4,... : server hostnames
*/
query_remap_info *qri = static_cast<query_remap_info *>(TSmalloc(sizeof(query_remap_info)));
qri->param_name = TSstrdup(argv[2]);
qri->param_len = strlen(qri->param_name);
qri->num_hosts = argc - 3;
qri->hosts = static_cast<char **>(TSmalloc(qri->num_hosts * sizeof(char *)));
Dbg(dbg_ctl, " - Hash using query parameter [%s] with %d hosts", qri->param_name, qri->num_hosts);
for (i = 0; i < qri->num_hosts; ++i) {
qri->hosts[i] = TSstrdup(argv[i + 3]);
Dbg(dbg_ctl, " - Host %d: %s", i, qri->hosts[i]);
}
Another way remap plugins may want handle more complex configuration is
to specify a configuration filename as a pparam
and parse the
specified file during instance initialization.
Performing the Remap¶
The plugin implements the tsremap_remap
function, which is called
when TS has read the client HTTP request headers and matched the request
to a remap rule configured for the plugin. The TSRemapRequestInfo
struct contains input and output members for the remap operation.
tsremap_remap
uses the configuration information passed via the
ihandle
and checks the request_query
for the configured query
parameter. If the parameter is found, the plugin sets a new_host
to
modify the request host:
TSfree(qri);
}
}
TSRemapStatus
TSRemapDoRemap(void *ih, TSHttpTxn rh ATS_UNUSED, TSRemapRequestInfo *rri)
{
query_remap_info *qri = static_cast<query_remap_info *>(ih);
if (!qri || !rri) {
TSError("[%s] null private data or RRI", PLUGIN_NAME);
return TSREMAP_NO_REMAP;
}
int req_query_len;
const char *req_query = TSUrlHttpQueryGet(rri->requestBufp, rri->requestUrl, &req_query_len);
if (req_query && req_query_len > 0) {
char *q, *key;
char *s = nullptr;
int hostidx = -1;
/* make a copy of the query, as it is read only */
q = TSstrndup(req_query, req_query_len + 1);
/* parse query parameters */
for (key = strtok_r(q, "&", &s); key != nullptr;) {
char *val = strchr(key, '=');
if (val && static_cast<size_t>(val - key) == qri->param_len && !strncmp(key, qri->param_name, qri->param_len)) {
++val;
/* the param key matched the configured param_name
hash the param value to pick a host */
hostidx = hash_fnv32(val, strlen(val)) % static_cast<uint32_t>(qri->num_hosts);
Dbg(dbg_ctl, "modifying host based on %s", key);
break;
}
key = strtok_r(nullptr, "&", &s);
}
TSfree(q);
if (hostidx >= 0) {
int req_host_len;
/* TODO: Perhaps use dbg_ctl.on() before calling TSUrlHostGet()... */
const char *req_host = TSUrlHostGet(rri->requestBufp, rri->requestUrl, &req_host_len);
if (TSUrlHostSet(rri->requestBufp, rri->requestUrl, qri->hosts[hostidx], strlen(qri->hosts[hostidx])) != TS_SUCCESS) {
Dbg(dbg_ctl, "Failed to modify the Host in request URL");
return TSREMAP_NO_REMAP;
}
Dbg(dbg_ctl, "host changed from [%.*s] to [%s]", req_host_len, req_host, qri->hosts[hostidx]);
return TSREMAP_DID_REMAP; /* host has been modified */
}
}