[ https://issues.apache.org/jira/browse/TS-4395?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15357378#comment-15357378 ]
ASF GitHub Bot commented on TS-4395: ------------------------------------ Github user jpeach commented on a diff in the pull request: https://github.com/apache/trafficserver/pull/747#discussion_r69163526 --- Diff: plugins/experimental/remap_purge/remap_purge.c --- @@ -0,0 +1,335 @@ +/** @file + + Per-remap purge RESTful API for stateful generation ID management. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <getopt.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "ts/ts.h" +#include "ts/remap.h" +#include "ts/ink_defs.h" + +static const char *PLUGIN_NAME = "remap_purge"; +static const char *DEFAULT_DIR = "var/trafficserver"; /* Not perfect, but no better API) */ + +typedef struct PurgeInstance_t { + char *id; + char *secret; + int secret_len; + char *header; + int header_len; + char *state_file; + bool allow_get; + int64_t gen_id; + TSMutex lock; +} PurgeInstance; + +static char * +make_state_path(const char *filename) +{ + if ('/' == *filename) { + return TSstrdup(filename); + } else { + char buf[8192]; + struct stat s; + const char *dir = TSInstallDirGet(); + + snprintf(buf, sizeof(buf) - 1, "%s/%s/%s", dir, DEFAULT_DIR, PLUGIN_NAME); + if (-1 == stat(buf, &s)) { + if (ENOENT == errno) { + if (-1 == mkdir(buf, S_IRWXU)) { + TSError("[%s] Unable to create directory %s: errno=%d", PLUGIN_NAME, buf, errno); + return NULL; + } + } else { + TSError("[%s] Unable to stat() directory %s: errno=%d", PLUGIN_NAME, buf, errno); + return NULL; + } + } else { + if (!S_ISDIR(s.st_mode)) { + TSError("[%s] Can not create directory %s, file exists", PLUGIN_NAME, buf); + return NULL; + } + } + snprintf(buf, sizeof(buf) - 1, "%s/%s/%s/%s.genid", dir, DEFAULT_DIR, PLUGIN_NAME, filename); + return TSstrdup(buf); + } + + return NULL; +} + +/* Constructor and destructor for the PurgeInstance */ +static void +init_purge_instance(PurgeInstance *purge, char *id) +{ + FILE *file = fopen(purge->state_file, "r"); + + if (file) { + fscanf(file, "%" PRId64 "", &purge->gen_id); + TSDebug(PLUGIN_NAME, "Read genID from %s for %s", purge->state_file, purge->id); + } + + /* If not specified, we set the ID tag to the fromURL from the remap rule that triggered */ + if (!purge->id) { + purge->id = TSstrdup(id); + } + + purge->lock = TSMutexCreate(); +} + +static void +delete_purge_instance(PurgeInstance *purge) +{ + if (purge) { + TSfree(purge->id); + TSfree(purge->state_file); + TSfree(purge->secret); + TSfree(purge->header); + TSMutexDestroy(purge->lock); + TSfree(purge); + } +} + +/* This is where we start the PURGE events, setting up the transactino to fail, + and bump the generation ID, and finally save the state. */ +static int +on_http_cache_lookup_complete(TSHttpTxn txnp, TSCont contp, PurgeInstance *purge) +{ + FILE *file; + + TSMutexLock(purge->lock); + + ++purge->gen_id; + TSDebug(PLUGIN_NAME, "Bumping the Generation ID to %" PRId64 " for %s", purge->gen_id, purge->id); + + if ((file = fopen(purge->state_file, "w"))) { + TSDebug(PLUGIN_NAME, "\tsaving state to %s", purge->state_file); + fprintf(file, "%" PRId64 "", purge->gen_id); + fclose(file); + } else { + TSError("[%s] Unable to save state to file %s: errno=%d", PLUGIN_NAME, purge->state_file, errno); + } + + TSMutexUnlock(purge->lock); + + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); + return TS_SUCCESS; +} + +/* Before we can send the response, we want to modify it to a "200 OK" again, + and produce some reasonabel body output. */ +static int +on_send_response_header(TSHttpTxn txnp, TSCont contp, PurgeInstance *purge) +{ + TSMBuffer bufp; + TSMLoc hdr_loc; + + TSDebug(PLUGIN_NAME, "Fixing up the response on the succseful PURGE"); + if (TS_SUCCESS == TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc)) { + char response[1024]; + int len = snprintf(response, sizeof(response) - 1, "PURGED %s\r\n\r\n", purge->id); + ; + + TSHttpHdrStatusSet(bufp, hdr_loc, TS_HTTP_STATUS_OK); + TSHttpHdrReasonSet(bufp, hdr_loc, "OK", 2); + TSHttpTxnErrorBodySet(txnp, TSstrdup(response), len, NULL); + + TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + } else { + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); + } + + return TS_SUCCESS; +} + +/* This is the main continuation, triggered after DoRemap has decided we should + handle this request internally. */ +static int +purge_cont(TSCont contp, TSEvent event, void *edata) +{ + TSHttpTxn txnp = (TSHttpTxn)edata; + PurgeInstance *purge = (PurgeInstance *)TSContDataGet(contp); + + switch (event) { + case TS_EVENT_HTTP_SEND_RESPONSE_HDR: + return on_send_response_header(txnp, contp, purge); + break; + + case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: + return on_http_cache_lookup_complete(txnp, contp, purge); + break; + + default: + TSDebug(PLUGIN_NAME, "Unexpected event: %d", event); + break; + } + + return TS_SUCCESS; +} + +static void +handle_purge(TSHttpTxn txnp, PurgeInstance *purge) +{ + TSMBuffer reqp; + TSMLoc hdr_loc = NULL, url_loc = NULL; + bool should_purge = false; + + if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc)) { + /* First see if we require the "secret" to be passed in a header, and then use that */ + if (purge->header) { + TSMLoc field_loc = TSMimeHdrFieldFind(reqp, hdr_loc, purge->header, purge->header_len); + + if (field_loc) { + const char *header; + int header_len; + + header = TSMimeHdrFieldValueStringGet(reqp, hdr_loc, field_loc, -1, &header_len); + TSDebug(PLUGIN_NAME, "Checking for %.*s == %s ?", header_len, header, purge->secret); + if (header && (header_len == purge->secret_len) && !memcmp(header, purge->secret, header_len)) { + should_purge = true; + } + TSHandleMLocRelease(reqp, hdr_loc, field_loc); + } + } else { + /* We are matching on the path component instead of a header */ + if (TS_SUCCESS == TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc)) { + int path_len = 0, method_len = 0; + const char *path = TSUrlPathGet(reqp, url_loc, &path_len); + const char *method = TSHttpHdrMethodGet(reqp, hdr_loc, &method_len); + + TSDebug(PLUGIN_NAME, "Checking PATH = %.*s", path_len, path); + if (((TS_HTTP_METHOD_PURGE == method) || ((TS_HTTP_METHOD_GET == method) && purge->allow_get)) && path && + (path_len >= purge->secret_len) && !memcmp(path + (path_len - purge->secret_len), purge->secret, purge->secret_len)) { + should_purge = true; + } + TSHandleMLocRelease(reqp, hdr_loc, url_loc); + } + } + TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc); + } + + /* Setup the continuation to handle this request if appropriate, if not, set the GenID if needed */ + if (should_purge) { + TSCont cont = TSContCreate(purge_cont, TSMutexCreate()); + + TSContDataSet(cont, purge); + TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont); + TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); + } else if (purge->gen_id > 0) { + TSHttpTxnConfigIntSet(txnp, TS_CONFIG_HTTP_CACHE_GENERATION, purge->gen_id); + } +} + +TSReturnCode +TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) +{ + TSDebug(PLUGIN_NAME, "initialized"); + return TS_SUCCESS; +} + +TSReturnCode +TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size) +{ + char *id = argv[0]; /* The ID is default to the "from" URL, so save it */ + PurgeInstance *purge = TSmalloc(sizeof(PurgeInstance)); + static const struct option longopt[] = { + {(char *)"id", required_argument, NULL, 'i'}, {(char *)"secret", required_argument, NULL, 's'}, + {(char *)"header", required_argument, NULL, 'h'}, {(char *)"state_file", required_argument, NULL, 'f'}, + {(char *)"allow_get", no_argument, NULL, 'a'}, {NULL, no_argument, NULL, '\0'}, + }; + + memset(purge, 0, sizeof(PurgeInstance)); + + // The first two arguments are the "from" and "to" URL string. We need to + // skip them, but we also require that there be an option to masquerade as + // argv[0], so we increment the argument indexes by 1 rather than by 2. + argc--; + argv++; + optind = 0; + + for (;;) { + int opt = getopt_long(argc, (char *const *)argv, "", longopt, NULL); + + if (opt == -1) { + break; + } + + switch (opt) { + case 'a': + purge->allow_get = true; + break; + case 'h': + purge->header = TSstrdup(optarg); + purge->header_len = strlen(purge->header); + break; + case 'i': + purge->id = TSstrdup(optarg); + break; + case 's': + purge->secret = TSstrdup(optarg); + purge->secret_len = strlen(purge->secret); + break; + case 'f': + purge->state_file = make_state_path(optarg); + break; + } + } + + if ((NULL == purge->secret) || (NULL == purge->state_file) || !purge->secret_len) { + TSError("[%s] Unable to create remap instance, need at least a secret (--secret) and state (--state_file)", PLUGIN_NAME); --- End diff -- Delete the purge instance? > conf_purge: Simple plugin to purge an entire remap rule > ------------------------------------------------------- > > Key: TS-4395 > URL: https://issues.apache.org/jira/browse/TS-4395 > Project: Traffic Server > Issue Type: New Feature > Components: Plugins > Reporter: Leif Hedstrom > Assignee: Leif Hedstrom > Fix For: 7.0.0 > > > This is similar to the existing plugin to purge based on a genID stored in a > persistent storage. The difference is that the purging is done exclusively > via a restful API, and has little (no) overhead on performance (since the > generation ID is always in memory). > Example: > {code} > map http://example.com/p1 http://p1.example.com > @plugin=remap_purge.so @pparam=--path=__secret_purge__ > @pparam=--state=example_p1 > {code} > And to purge: > {code} > $ curl -s -D - -X PURGE http://example.com/p1/__secret_purge__ > HTTP/1.1 200 OK > Date: Sat, 30 Apr 2016 00:09:34 GMT > Connection: close > Server: ATS/7.0.0 > Content-Length: 39 > Content-Type: text/html > PURGED http://example.com/p1 > {code} -- This message was sent by Atlassian JIRA (v6.3.4#6332)