| /** @file |
| |
| Proxy Side Include plugin (PSI) |
| |
| Synopsis: |
| |
| This plugin allows to insert the content of a file stored on the proxy disk |
| into the body of an html response. |
| |
| The plugin illustrates how to use a pool of threads in order to do blocking |
| calls (here, some disk i/o) in a Traffic Server plugin. |
| |
| Further details: Refer to README file. |
| |
| @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 <limits.h> |
| #include <string.h> |
| #include <sys/param.h> |
| |
| #include "ts/ts.h" |
| #include "thread.h" |
| #include "tscore/ink_defs.h" |
| |
| /* This is the number of threads spawned by the plugin. |
| Should be tuned based on performance requirements, |
| blocking calls duration, etc... */ |
| #define NB_THREADS 3 |
| |
| #define PSI_FILENAME_MAX_SIZE 512 |
| #define PSI_PATH_MAX_SIZE 256 |
| #define PSI_PATH "include" |
| |
| #define PSI_START_TAG "<!--include=" |
| #define PSI_START_TAG_LEN 12 |
| |
| #define PSI_END_TAG "-->" |
| #define PSI_END_TAG_LEN 3 |
| |
| #define MIME_FIELD_XPSI "X-Psi" |
| |
| typedef enum { |
| STATE_READ_DATA = 1, |
| STATE_READ_PSI = 2, |
| STATE_DUMP_PSI = 3, |
| } PluginState; |
| |
| typedef enum { |
| PARSE_SEARCH, |
| PARSE_EXTRACT, |
| } ParseState; |
| |
| typedef struct { |
| unsigned int magic; |
| TSVIO output_vio; |
| TSIOBuffer output_buffer; |
| TSIOBufferReader output_reader; |
| |
| TSIOBuffer psi_buffer; |
| TSIOBufferReader psi_reader; |
| char psi_filename[PSI_FILENAME_MAX_SIZE + 128]; |
| int psi_filename_len; |
| int psi_success; |
| |
| ParseState parse_state; |
| |
| PluginState state; |
| int transform_bytes; |
| } ContData; |
| |
| typedef struct { |
| TSCont contp; |
| TSEvent event; |
| } TryLockData; |
| |
| typedef enum { |
| STR_SUCCESS, |
| STR_PARTIAL, |
| STR_FAIL, |
| } StrOperationResult; |
| |
| extern Queue job_queue; |
| |
| static TSTextLogObject log; |
| static char psi_directory[PSI_PATH_MAX_SIZE]; |
| |
| static int trylock_handler(TSCont contp, TSEvent event, void *edata); |
| |
| /*------------------------------------------------------------------------- |
| cont_data_alloc |
| Allocate and initialize a ContData structure associated to a transaction |
| |
| Input: |
| Output: |
| Return Value: |
| Pointer on a new allocated ContData structure |
| -------------------------------------------------------------------------*/ |
| static ContData * |
| cont_data_alloc() |
| { |
| ContData *data; |
| |
| data = (ContData *)TSmalloc(sizeof(ContData)); |
| data->magic = MAGIC_ALIVE; |
| data->output_vio = NULL; |
| data->output_buffer = NULL; |
| data->output_reader = NULL; |
| |
| data->psi_buffer = NULL; |
| data->psi_reader = NULL; |
| data->psi_filename[0] = '\0'; |
| data->psi_filename_len = 0; |
| data->psi_success = 0; |
| |
| data->parse_state = PARSE_SEARCH; |
| |
| data->state = STATE_READ_DATA; |
| data->transform_bytes = 0; |
| |
| return data; |
| } |
| |
| /*------------------------------------------------------------------------- |
| cont_data_destroy |
| Deallocate ContData structure associated to a transaction |
| |
| Input: |
| data structure to deallocate |
| Output: |
| Return Value: |
| none |
| -------------------------------------------------------------------------*/ |
| static void |
| cont_data_destroy(ContData *data) |
| { |
| TSDebug(PLUGIN_NAME, "Destroying continuation data"); |
| if (data) { |
| TSAssert(data->magic == MAGIC_ALIVE); |
| if (data->output_reader) { |
| TSIOBufferReaderFree(data->output_reader); |
| data->output_reader = NULL; |
| } |
| if (data->output_buffer) { |
| TSIOBufferDestroy(data->output_buffer); |
| data->output_buffer = NULL; |
| } |
| if (data->psi_reader) { |
| TSIOBufferReaderFree(data->psi_reader); |
| data->psi_reader = NULL; |
| } |
| if (data->psi_buffer) { |
| TSIOBufferDestroy(data->psi_buffer); |
| data->psi_buffer = NULL; |
| } |
| data->magic = MAGIC_DEAD; |
| TSfree(data); |
| } |
| } |
| |
| /*------------------------------------------------------------------------- |
| strsearch_ioreader |
| Looks for string pattern in an iobuffer |
| |
| Input: |
| reader reader on a iobuffer |
| pattern string to look for (nul terminated) |
| Output: |
| nparse number of chars scanned, excluding the matching pattern |
| Return Value: |
| STR_SUCCESS if pattern found |
| STR_PARTIAL if pattern found partially |
| STR_FAIL if pattern not found |
| -------------------------------------------------------------------------*/ |
| static StrOperationResult |
| strsearch_ioreader(TSIOBufferReader reader, const char *pattern, int *nparse) |
| { |
| int index = 0; |
| TSIOBufferBlock block = TSIOBufferReaderStart(reader); |
| int slen = strlen(pattern); |
| |
| if (slen <= 0) { |
| return STR_FAIL; |
| } |
| |
| *nparse = 0; |
| |
| /* Loop thru each block while we've not yet found the pattern */ |
| while ((block != NULL) && (index < slen)) { |
| int64_t blocklen; |
| const char *blockptr = TSIOBufferBlockReadStart(block, reader, &blocklen); |
| const char *ptr; |
| |
| for (ptr = blockptr; ptr < blockptr + blocklen; ptr++) { |
| (*nparse)++; |
| if (*ptr == pattern[index]) { |
| index++; |
| if (index == slen) { |
| break; |
| } |
| } else { |
| index = 0; |
| } |
| } |
| |
| /* Parse next block */ |
| block = TSIOBufferBlockNext(block); |
| } |
| |
| *nparse -= index; /* Adjust nparse so it doesn't include matching chars */ |
| if (index == slen) { |
| TSDebug(PLUGIN_NAME, "strfind: match for %s at position %d", pattern, *nparse); |
| return STR_SUCCESS; |
| } else if (index > 0) { |
| TSDebug(PLUGIN_NAME, "strfind: partial match for %s at position %d", pattern, *nparse); |
| return STR_PARTIAL; |
| } else { |
| TSDebug(PLUGIN_NAME, "strfind no match for %s", pattern); |
| return STR_FAIL; |
| } |
| } |
| |
| /*------------------------------------------------------------------------- |
| strextract_ioreader |
| Extract a string from an iobuffer. |
| Start reading at position offset in iobuffer and extract until the |
| string end_pattern is found. |
| |
| Input: |
| reader reader on a iobuffer |
| offset position to start reading |
| end_pattern the termination string (nul terminated) |
| Output: |
| buffer if success, contains the extracted string, nul terminated |
| buflen if success, contains the buffer length (excluding null char). |
| Return Value: |
| STR_SUCCESS if extraction successful |
| STR_PARTIAL if extraction not yet completed |
| STR_FAIL if extraction failed |
| -------------------------------------------------------------------------*/ |
| static int |
| strextract_ioreader(TSIOBufferReader reader, int offset, const char *end_pattern, char *buffer, int *buflen) |
| { |
| int buf_idx = 0; |
| int p_idx = 0; |
| int nbytes_so_far = 0; |
| int plen = strlen(end_pattern); |
| const char *ptr; |
| TSIOBufferBlock block = TSIOBufferReaderStart(reader); |
| |
| if (plen <= 0) { |
| return STR_FAIL; |
| } |
| |
| /* Now start extraction */ |
| while ((block != NULL) && (p_idx < plen) && (buf_idx < PSI_FILENAME_MAX_SIZE)) { |
| int64_t blocklen; |
| const char *blockptr = TSIOBufferBlockReadStart(block, reader, &blocklen); |
| |
| for (ptr = blockptr; ptr < blockptr + blocklen; ptr++, nbytes_so_far++) { |
| if (nbytes_so_far >= offset) { |
| /* Add a new character to the filename */ |
| buffer[buf_idx++] = *ptr; |
| |
| /* If we have reach the end of the filename, we're done */ |
| if (end_pattern[p_idx] == *ptr) { |
| p_idx++; |
| if (p_idx == plen) { |
| break; |
| } |
| } else { |
| p_idx = 0; |
| } |
| |
| /* The filename is too long, something is fishy... let's abort extraction */ |
| if (buf_idx >= PSI_FILENAME_MAX_SIZE) { |
| break; |
| } |
| } |
| } |
| |
| block = TSIOBufferBlockNext(block); |
| } |
| |
| /* Error, could not read end of filename */ |
| if (buf_idx >= PSI_FILENAME_MAX_SIZE) { |
| TSDebug(PLUGIN_NAME, "strextract: filename too long"); |
| *buflen = 0; |
| return STR_FAIL; |
| } |
| |
| /* Full Match */ |
| if (p_idx == plen) { |
| /* Nul terminate the filename, remove the end_pattern copied into the buffer */ |
| *buflen = buf_idx - plen; |
| buffer[*buflen] = '\0'; |
| TSDebug(PLUGIN_NAME, "strextract: filename = |%s|", buffer); |
| return STR_SUCCESS; |
| } |
| /* End of filename not yet reached we need to read some more data */ |
| else { |
| TSDebug(PLUGIN_NAME, "strextract: partially extracted filename"); |
| *buflen = buf_idx - p_idx; |
| return STR_PARTIAL; |
| } |
| } |
| |
| /*------------------------------------------------------------------------- |
| parse_data |
| Search for psi filename in the data. |
| |
| Input: |
| contp continuation for the current transaction |
| reader reader on the iobuffer that contains data |
| avail amount of data available in the iobuffer |
| Output: |
| towrite amount of data in the iobuffer that can be written |
| to the downstream vconnection |
| toconsume amount of data in the iobuffer to consume |
| Return Value: |
| 0 if no psi filename found |
| 1 if a psi filename was found |
| -------------------------------------------------------------------------*/ |
| static int |
| parse_data(TSCont contp, TSIOBufferReader input_reader, int avail, int *toconsume, int *towrite) |
| { |
| ContData *data; |
| int nparse = 0; |
| int status; |
| |
| data = TSContDataGet(contp); |
| TSAssert(data->magic == MAGIC_ALIVE); |
| |
| if (data->parse_state == PARSE_SEARCH) { |
| /* Search for the start pattern */ |
| status = strsearch_ioreader(input_reader, PSI_START_TAG, &nparse); |
| switch (status) { |
| case STR_FAIL: |
| /* We didn't found the pattern */ |
| *toconsume = avail; |
| *towrite = avail; |
| data->parse_state = PARSE_SEARCH; |
| return 0; |
| case STR_PARTIAL: |
| /* We need to read some more data */ |
| *toconsume = nparse; |
| *towrite = nparse; |
| data->parse_state = PARSE_SEARCH; |
| return 0; |
| case STR_SUCCESS: |
| /* We found the start_pattern, let's go ahead */ |
| data->psi_filename_len = 0; |
| data->psi_filename[0] = '\0'; |
| data->parse_state = PARSE_EXTRACT; |
| break; |
| default: |
| TSAssert(!"strsearch_ioreader returned unexpected status"); |
| } |
| } |
| |
| /* And now let's extract the filename */ |
| status = strextract_ioreader(input_reader, nparse + PSI_START_TAG_LEN, PSI_END_TAG, data->psi_filename, &data->psi_filename_len); |
| switch (status) { |
| case STR_FAIL: |
| /* We couldn't extract a valid filename */ |
| *toconsume = nparse; |
| *towrite = nparse; |
| data->parse_state = PARSE_SEARCH; |
| return 0; |
| case STR_PARTIAL: |
| /* We need to read some more data */ |
| *toconsume = nparse; |
| *towrite = nparse; |
| data->parse_state = PARSE_EXTRACT; |
| return 0; |
| case STR_SUCCESS: |
| /* We got a valid filename */ |
| *toconsume = nparse + PSI_START_TAG_LEN + data->psi_filename_len + PSI_END_TAG_LEN; |
| *towrite = nparse; |
| data->parse_state = PARSE_SEARCH; |
| return 1; |
| default: |
| TSAssert(!"strextract_ioreader returned bad status"); |
| } |
| |
| return 0; |
| } |
| |
| // TODO: Use libc basename function |
| // |
| /*------------------------------------------------------------------------- |
| strip_path |
| Utility func to strip path from a filename (= _basename cmd on unix) |
| Input: |
| filename |
| Output : |
| None |
| Return Value: |
| Filename with path stripped |
| -------------------------------------------------------------------------*/ |
| static const char * |
| _basename(const char *filename) |
| { |
| char *cptr; |
| const char *ptr = filename; |
| |
| while ((cptr = strchr(ptr, (int)'/')) != NULL) { |
| ptr = cptr + 1; |
| } |
| return ptr; |
| } |
| |
| /*------------------------------------------------------------------------- |
| psi_include |
| Read file to include. Copy its content into an iobuffer. |
| |
| This is the function doing blocking calls and called by the plugin's threads |
| |
| Input: |
| data continuation for the current transaction |
| Output : |
| data->psi_buffer contains the file content |
| data->psi_sucess 0 if include failed, 1 if success |
| Return Value: |
| 0 if failure |
| 1 if success |
| -------------------------------------------------------------------------*/ |
| static int |
| psi_include(TSCont contp, void *edata ATS_UNUSED) |
| { |
| #define BUFFER_SIZE 1024 |
| ContData *data; |
| TSFile filep; |
| char buf[BUFFER_SIZE]; |
| char inc_file[PSI_PATH_MAX_SIZE + PSI_FILENAME_MAX_SIZE]; |
| |
| /* We manipulate plugin continuation data from a separate thread. |
| Grab mutex to avoid concurrent access */ |
| TSMutexLock(TSContMutexGet(contp)); |
| data = TSContDataGet(contp); |
| TSAssert(data->magic == MAGIC_ALIVE); |
| |
| if (!data->psi_buffer) { |
| data->psi_buffer = TSIOBufferCreate(); |
| data->psi_reader = TSIOBufferReaderAlloc(data->psi_buffer); |
| } |
| |
| /* For security reason, we do not allow to include files that are |
| not in the directory <plugin_path>/include. |
| Also include file cannot contain any path. */ |
| sprintf(inc_file, "%s/%s", psi_directory, _basename(data->psi_filename)); |
| |
| /* Read the include file and copy content into iobuffer */ |
| if ((filep = TSfopen(inc_file, "r")) != NULL) { |
| TSDebug(PLUGIN_NAME, "Reading include file %s", inc_file); |
| |
| while (TSfgets(filep, buf, BUFFER_SIZE) != NULL) { |
| TSIOBufferBlock block; |
| int64_t len, avail, ndone, ntodo, towrite; |
| char *ptr_block; |
| |
| len = strlen(buf); |
| ndone = 0; |
| ntodo = len; |
| while (ntodo > 0) { |
| /* TSIOBufferStart allocates more blocks if required */ |
| block = TSIOBufferStart(data->psi_buffer); |
| ptr_block = TSIOBufferBlockWriteStart(block, &avail); |
| towrite = MIN(ntodo, avail); |
| |
| memcpy(ptr_block, buf + ndone, towrite); |
| TSIOBufferProduce(data->psi_buffer, towrite); |
| ntodo -= towrite; |
| ndone += towrite; |
| } |
| } |
| TSfclose(filep); |
| data->psi_success = 1; |
| if (log) { |
| TSTextLogObjectWrite(log, "Successfully included file: %s", inc_file); |
| } |
| } else { |
| data->psi_success = 0; |
| if (log) { |
| TSTextLogObjectWrite(log, "Failed to include file: %s", inc_file); |
| } |
| } |
| |
| /* Change state and schedule an event EVENT_IMMEDIATE on the plugin continuation |
| to let it know we're done. */ |
| |
| /* Note: if the blocking call was not in the transformation state (i.e. in |
| TS_HTTP_READ_REQUEST_HDR, TS_HTTP_OS_DNS and so on...) we could |
| use TSHttpTxnReenable to wake up the transaction instead of sending an event. */ |
| |
| TSContSchedule(contp, 0, TS_THREAD_POOL_DEFAULT); |
| data->psi_success = 0; |
| data->state = STATE_READ_DATA; |
| TSMutexUnlock(TSContMutexGet(contp)); |
| |
| return 0; |
| } |
| |
| /*------------------------------------------------------------------------- |
| wake_up_streams |
| Send an event to the upstream vconnection to either |
| - ask for more data |
| - let it know we're done |
| Reenable the downstream vconnection |
| Input: |
| contp continuation for the current transaction |
| Output : |
| Return Value: |
| 0 if failure |
| 1 if success |
| -------------------------------------------------------------------------*/ |
| static int |
| wake_up_streams(TSCont contp) |
| { |
| TSVIO input_vio; |
| ContData *data; |
| int ntodo; |
| |
| data = TSContDataGet(contp); |
| TSAssert(data->magic == MAGIC_ALIVE); |
| |
| input_vio = TSVConnWriteVIOGet(contp); |
| ntodo = TSVIONTodoGet(input_vio); |
| |
| if (ntodo > 0) { |
| TSVIOReenable(data->output_vio); |
| TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio); |
| } else { |
| TSDebug(PLUGIN_NAME, "Total bytes produced by transform = %d", data->transform_bytes); |
| TSVIONBytesSet(data->output_vio, data->transform_bytes); |
| TSVIOReenable(data->output_vio); |
| TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio); |
| } |
| |
| return 1; |
| } |
| |
| /*------------------------------------------------------------------------- |
| handle_transform |
| Get data from upstream vconn. |
| Parse it. |
| Include file if include tags found. |
| Copy data to downstream vconn. |
| Wake up upstream to get more data. |
| |
| Input: |
| contp continuation for the current transaction |
| Output : |
| Return Value: |
| 0 if failure |
| 1 if success |
| -------------------------------------------------------------------------*/ |
| static int |
| handle_transform(TSCont contp) |
| { |
| TSVConn output_conn; |
| TSVIO input_vio; |
| ContData *data; |
| TSIOBufferReader input_reader; |
| int toread, avail, psi, toconsume = 0, towrite = 0; |
| |
| /* Get the output (downstream) vconnection where we'll write data to. */ |
| output_conn = TSTransformOutputVConnGet(contp); |
| |
| /* Get upstream vio */ |
| input_vio = TSVConnWriteVIOGet(contp); |
| data = TSContDataGet(contp); |
| TSAssert(data->magic == MAGIC_ALIVE); |
| |
| if (!data->output_buffer) { |
| data->output_buffer = TSIOBufferCreate(); |
| data->output_reader = TSIOBufferReaderAlloc(data->output_buffer); |
| |
| /* INT64_MAX because we don't know yet how much bytes we'll produce */ |
| data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, INT64_MAX); |
| } |
| |
| /* If the input VIO's buffer is NULL, the transformation is over */ |
| if (!TSVIOBufferGet(input_vio)) { |
| TSDebug(PLUGIN_NAME, "input_vio NULL, terminating transformation"); |
| TSVIONBytesSet(data->output_vio, data->transform_bytes); |
| TSVIOReenable(data->output_vio); |
| return 1; |
| } |
| |
| /* Determine how much data we have left to read. */ |
| toread = TSVIONTodoGet(input_vio); |
| |
| if (toread > 0) { |
| input_reader = TSVIOReaderGet(input_vio); |
| avail = TSIOBufferReaderAvail(input_reader); |
| |
| /* There are some data available for reading. Let's parse it */ |
| if (avail > 0) { |
| /* No need to parse data if there are too few bytes left to contain |
| an include command... */ |
| if (toread > (PSI_START_TAG_LEN + PSI_END_TAG_LEN)) { |
| psi = parse_data(contp, input_reader, avail, &toconsume, &towrite); |
| } else { |
| towrite = avail; |
| toconsume = avail; |
| psi = 0; |
| } |
| |
| if (towrite > 0) { |
| /* Update the total size of the doc so far */ |
| data->transform_bytes += towrite; |
| |
| /* Copy the data from the read buffer to the output buffer. */ |
| /* TODO: Should we check the return value of TSIOBufferCopy() ? */ |
| TSIOBufferCopy(TSVIOBufferGet(data->output_vio), TSVIOReaderGet(input_vio), towrite, 0); |
| /* Reenable the output connection so it can read the data we've produced. */ |
| TSVIOReenable(data->output_vio); |
| } |
| |
| if (toconsume > 0) { |
| /* Consume data we've processed an we are no longer interested in */ |
| TSIOBufferReaderConsume(input_reader, toconsume); |
| |
| /* Modify the input VIO to reflect how much data we've completed. */ |
| TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + toconsume); |
| } |
| |
| /* Did we find a psi filename to execute in the data ? */ |
| if (psi) { |
| Job *new_job; |
| /* Add a request to include a file into the jobs queue.. */ |
| /* We'll be called back once it's done with an EVENT_IMMEDIATE */ |
| TSDebug(PLUGIN_NAME, "Psi filename extracted, adding an include job to thread queue"); |
| data->state = STATE_READ_PSI; |
| |
| /* Create a new job request and add it to the queue */ |
| new_job = job_create(contp, &psi_include, NULL); |
| add_to_queue(&job_queue, new_job); |
| |
| /* Signal to the threads there is a new job */ |
| thread_signal_job(); |
| |
| return 1; |
| } |
| } |
| } |
| |
| /* Wake up upstream and downstream vconnections */ |
| wake_up_streams(contp); |
| |
| return 1; |
| } |
| |
| /*------------------------------------------------------------------------- |
| dump_psi |
| Dump the psi_output to the downstream vconnection. |
| |
| Input: |
| contp continuation for the current transaction |
| Output : |
| Return Value: |
| 0 if failure |
| 1 if success |
| -------------------------------------------------------------------------*/ |
| static int |
| dump_psi(TSCont contp) |
| { |
| ContData *data; |
| int psi_output_len; |
| |
| /* TODO: This is odd, do we need to get the input_vio, but never use it ?? */ |
| #if 0 |
| TSVIO input_vio; |
| input_vio = TSVConnWriteVIOGet(contp); |
| #endif |
| |
| data = TSContDataGet(contp); |
| TSAssert(data->magic == MAGIC_ALIVE); |
| |
| /* If script exec succeeded, copy its output to the downstream vconn */ |
| if (data->psi_success == 1) { |
| psi_output_len = TSIOBufferReaderAvail(data->psi_reader); |
| |
| if (psi_output_len > 0) { |
| data->transform_bytes += psi_output_len; |
| |
| TSDebug(PLUGIN_NAME, "Inserting %d bytes from include file", psi_output_len); |
| /* TODO: Should we check the return value of TSIOBufferCopy() ? */ |
| TSIOBufferCopy(TSVIOBufferGet(data->output_vio), data->psi_reader, psi_output_len, 0); |
| /* Consume all the output data */ |
| TSIOBufferReaderConsume(data->psi_reader, psi_output_len); |
| |
| /* Reenable the output connection so it can read the data we've produced. */ |
| TSVIOReenable(data->output_vio); |
| } |
| } |
| |
| /* Change state to finish up reading upstream data */ |
| data->state = STATE_READ_DATA; |
| return 0; |
| } |
| |
| /*------------------------------------------------------------------------- |
| transform_handler |
| Handler for all events received during the transformation process |
| |
| Input: |
| contp continuation for the current transaction |
| event event received |
| data pointer on optional data |
| Output : |
| Return Value: |
| -------------------------------------------------------------------------*/ |
| static int |
| transform_handler(TSCont contp, TSEvent event, void *edata ATS_UNUSED) |
| { |
| TSVIO input_vio; |
| ContData *data; |
| int state, retval; |
| |
| /* This section will be called by both TS internal |
| and the thread. Protect it with a mutex to avoid |
| concurrent calls. */ |
| |
| /* Handle TryLock result */ |
| if (TSMutexLockTry(TSContMutexGet(contp)) != TS_SUCCESS) { |
| TSCont c = TSContCreate(trylock_handler, NULL); |
| TryLockData *d = TSmalloc(sizeof(TryLockData)); |
| |
| d->contp = contp; |
| d->event = event; |
| TSContDataSet(c, d); |
| TSContSchedule(c, 10, TS_THREAD_POOL_DEFAULT); |
| return 1; |
| } |
| |
| data = TSContDataGet(contp); |
| TSAssert(data->magic == MAGIC_ALIVE); |
| |
| state = data->state; |
| |
| /* Check to see if the transformation has been closed */ |
| retval = TSVConnClosedGet(contp); |
| if (retval) { |
| /* If the thread is still executing its job, we don't want to destroy |
| the continuation right away as the thread will call us back |
| on this continuation. */ |
| if (state == STATE_READ_PSI) { |
| TSContSchedule(contp, 10, TS_THREAD_POOL_DEFAULT); |
| } else { |
| TSMutexUnlock(TSContMutexGet(contp)); |
| cont_data_destroy(TSContDataGet(contp)); |
| TSContDestroy(contp); |
| return 1; |
| } |
| } else { |
| switch (event) { |
| case TS_EVENT_ERROR: |
| input_vio = TSVConnWriteVIOGet(contp); |
| TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio); |
| break; |
| |
| case TS_EVENT_VCONN_WRITE_COMPLETE: |
| TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1); |
| break; |
| |
| case TS_EVENT_VCONN_WRITE_READY: |
| /* downstream vconnection is done reading data we've write into it. |
| let's read some more data from upstream if we're in read state. */ |
| if (state == STATE_READ_DATA) { |
| handle_transform(contp); |
| } |
| break; |
| |
| case TS_EVENT_IMMEDIATE: |
| if (state == STATE_READ_DATA) { |
| /* upstream vconnection signals some more data ready to be read |
| let's try to transform some more data */ |
| handle_transform(contp); |
| } else if (state == STATE_DUMP_PSI) { |
| /* The thread scheduled an event on our continuation to let us |
| know it has completed its job |
| Let's dump the include content to the output vconnection */ |
| dump_psi(contp); |
| wake_up_streams(contp); |
| } |
| break; |
| |
| default: |
| TSAssert(!"Unexpected event"); |
| break; |
| } |
| } |
| |
| TSMutexUnlock(TSContMutexGet(contp)); |
| return 1; |
| } |
| |
| /*------------------------------------------------------------------------- |
| trylock_handler |
| Small handler to handle TSMutexLockTry failures |
| |
| Input: |
| contp continuation for the current transaction |
| event event received |
| data pointer on optional data |
| Output : |
| Return Value: |
| -------------------------------------------------------------------------*/ |
| static int |
| trylock_handler(TSCont contp, TSEvent event ATS_UNUSED, void *edata ATS_UNUSED) |
| { |
| TryLockData *data = TSContDataGet(contp); |
| transform_handler(data->contp, data->event, NULL); |
| TSfree(data); |
| TSContDestroy(contp); |
| return 0; |
| } |
| |
| /*------------------------------------------------------------------------- |
| transformable |
| Determine if the current transaction should be transformed or not |
| |
| Input: |
| txnp current transaction |
| Output : |
| Return Value: |
| 1 if transformable |
| 0 if not |
| -------------------------------------------------------------------------*/ |
| static int |
| transformable(TSHttpTxn txnp) |
| { |
| /* We are only interested in transforming "200 OK" responses |
| with a Content-Type: text/ header and with X-Psi header */ |
| TSMBuffer bufp; |
| TSMLoc hdr_loc, field_loc; |
| TSHttpStatus resp_status; |
| const char *value; |
| |
| if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc)) { |
| resp_status = TSHttpHdrStatusGet(bufp, hdr_loc); |
| if (resp_status != TS_HTTP_STATUS_OK) { |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| return 0; |
| } |
| |
| field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, -1); |
| if (field_loc == TS_NULL_MLOC) { |
| TSError("[%s] Unable to search Content-Type field", PLUGIN_NAME); |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| return 0; |
| } |
| |
| value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, NULL); |
| if ((value == NULL) || (strncasecmp(value, "text/", sizeof("text/") - 1) != 0)) { |
| TSHandleMLocRelease(bufp, hdr_loc, field_loc); |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| return 0; |
| } |
| |
| TSHandleMLocRelease(bufp, hdr_loc, field_loc); |
| |
| field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, MIME_FIELD_XPSI, -1); |
| |
| TSHandleMLocRelease(bufp, hdr_loc, field_loc); |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| } |
| |
| return 1; |
| } |
| |
| /*------------------------------------------------------------------------- |
| transform_add |
| Create a transformation and alloc data structure |
| |
| Input: |
| txnp current transaction |
| Output : |
| Return Value: |
| 1 if transformation created |
| 0 if not |
| -------------------------------------------------------------------------*/ |
| static int |
| transform_add(TSHttpTxn txnp) |
| { |
| TSCont contp; |
| ContData *data; |
| |
| contp = TSTransformCreate(transform_handler, txnp); |
| data = cont_data_alloc(); |
| TSContDataSet(contp, data); |
| |
| TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, contp); |
| return 1; |
| } |
| |
| /*------------------------------------------------------------------------- |
| read_response_handler |
| Handler for events related to hook READ_RESPONSE |
| |
| Input: |
| contp continuation for the current transaction |
| event event received |
| data pointer on eventual data |
| Output : |
| Return Value: |
| -------------------------------------------------------------------------*/ |
| static int |
| read_response_handler(TSCont contp ATS_UNUSED, TSEvent event, void *edata) |
| { |
| TSHttpTxn txnp = (TSHttpTxn)edata; |
| |
| switch (event) { |
| case TS_EVENT_HTTP_READ_RESPONSE_HDR: |
| if (transformable(txnp)) { |
| TSDebug(PLUGIN_NAME, "Add a transformation"); |
| transform_add(txnp); |
| } |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); |
| return 0; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /*------------------------------------------------------------------------- |
| TSPluginInit |
| Function called at plugin init time |
| |
| Input: |
| argc number of args |
| argv list vof args |
| Output : |
| Return Value: |
| -------------------------------------------------------------------------*/ |
| void |
| TSPluginInit(int argc ATS_UNUSED, const char *argv[] ATS_UNUSED) |
| { |
| TSPluginRegistrationInfo info; |
| int i; |
| TSReturnCode retval; |
| |
| info.plugin_name = "psi"; |
| info.vendor_name = "Apache"; |
| info.support_email = ""; |
| |
| if (TSPluginRegister(&info) != TS_SUCCESS) { |
| TSError("[%s] Plugin registration failed", PLUGIN_NAME); |
| } |
| |
| /* Initialize the psi directory = <plugin_path>/include */ |
| sprintf(psi_directory, "%s/%s", TSPluginDirGet(), PSI_PATH); |
| |
| /* create an TSTextLogObject to log any psi include */ |
| retval = TSTextLogObjectCreate("psi", TS_LOG_MODE_ADD_TIMESTAMP, &log); |
| if (retval == TS_ERROR) { |
| TSError("[%s] Failed creating log for psi plugin", PLUGIN_NAME); |
| log = NULL; |
| } |
| |
| /* Create working threads */ |
| thread_init(); |
| init_queue(&job_queue); |
| |
| for (i = 0; i < NB_THREADS; i++) { |
| char *thread_name = (char *)TSmalloc(64); |
| sprintf(thread_name, "Thread[%d]", i); |
| if (!TSThreadCreate(thread_loop, thread_name)) { |
| TSError("[%s] Failed creating threads", PLUGIN_NAME); |
| return; |
| } |
| } |
| |
| TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(read_response_handler, TSMutexCreate())); |
| TSDebug(PLUGIN_NAME, "Plugin started"); |
| } |