1 /** @file
2 
3   An example program that does a null transform of response body content.
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <inttypes.h>
27 
28 #include "ts/ts.h"
29 
30 #define PLUGIN_NAME "null_transform"
31 
32 typedef struct {
33   TSVIO output_vio;
34   TSIOBuffer output_buffer;
35   TSIOBufferReader output_reader;
36 } MyData;
37 
38 static MyData *
my_data_alloc()39 my_data_alloc()
40 {
41   MyData *data;
42 
43   data                = (MyData *)TSmalloc(sizeof(MyData));
44   data->output_vio    = NULL;
45   data->output_buffer = NULL;
46   data->output_reader = NULL;
47 
48   return data;
49 }
50 
51 static void
my_data_destroy(MyData * data)52 my_data_destroy(MyData *data)
53 {
54   if (data) {
55     if (data->output_buffer) {
56       TSIOBufferDestroy(data->output_buffer);
57     }
58     TSfree(data);
59   }
60 }
61 
62 static void
handle_transform(TSCont contp)63 handle_transform(TSCont contp)
64 {
65   TSVConn output_conn;
66   TSIOBuffer buf_test;
67   TSVIO input_vio;
68   MyData *data;
69   int64_t towrite;
70   int64_t avail;
71 
72   TSDebug(PLUGIN_NAME, "Entering handle_transform()");
73   /* Get the output (downstream) vconnection where we'll write data to. */
74 
75   output_conn = TSTransformOutputVConnGet(contp);
76 
77   /* Get the write VIO for the write operation that was performed on
78    * ourself. This VIO contains the buffer that we are to read from
79    * as well as the continuation we are to call when the buffer is
80    * empty. This is the input VIO (the write VIO for the upstream
81    * vconnection).
82    */
83   input_vio = TSVConnWriteVIOGet(contp);
84 
85   /* Get our data structure for this operation. The private data
86    * structure contains the output VIO and output buffer. If the
87    * private data structure pointer is NULL, then we'll create it
88    * and initialize its internals.
89    */
90   data = TSContDataGet(contp);
91   if (!data) {
92     data                = my_data_alloc();
93     data->output_buffer = TSIOBufferCreate();
94     data->output_reader = TSIOBufferReaderAlloc(data->output_buffer);
95     TSDebug(PLUGIN_NAME, "\tWriting %" PRId64 " bytes on VConn", TSVIONBytesGet(input_vio));
96     // data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, INT32_MAX);
97     data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, INT64_MAX);
98     // data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, TSVIONBytesGet(input_vio));
99     TSContDataSet(contp, data);
100   }
101 
102   /* We also check to see if the input VIO's buffer is non-NULL. A
103    * NULL buffer indicates that the write operation has been
104    * shutdown and that the upstream continuation does not want us to send any
105    * more WRITE_READY or WRITE_COMPLETE events. For this simplistic
106    * transformation that means we're done. In a more complex
107    * transformation we might have to finish writing the transformed
108    * data to our output connection.
109    */
110   buf_test = TSVIOBufferGet(input_vio);
111 
112   if (!buf_test) {
113     TSVIONBytesSet(data->output_vio, TSVIONDoneGet(input_vio));
114     TSVIOReenable(data->output_vio);
115     return;
116   }
117 
118   /* Determine how much data we have left to read. For this null
119    * transform plugin this is also the amount of data we have left
120    * to write to the output connection.
121    */
122   towrite = TSVIONTodoGet(input_vio);
123   TSDebug(PLUGIN_NAME, "\ttoWrite is %" PRId64 "", towrite);
124 
125   if (towrite > 0) {
126     /* The amount of data left to read needs to be truncated by
127      * the amount of data actually in the read buffer.
128      */
129     avail = TSIOBufferReaderAvail(TSVIOReaderGet(input_vio));
130     TSDebug(PLUGIN_NAME, "\tavail is %" PRId64 "", avail);
131     if (towrite > avail) {
132       towrite = avail;
133     }
134 
135     if (towrite > 0) {
136       /* Copy the data from the read buffer to the output buffer. */
137       TSIOBufferCopy(TSVIOBufferGet(data->output_vio), TSVIOReaderGet(input_vio), towrite, 0);
138 
139       /* Tell the read buffer that we have read the data and are no
140        * longer interested in it.
141        */
142       TSIOBufferReaderConsume(TSVIOReaderGet(input_vio), towrite);
143 
144       /* Modify the input VIO to reflect how much data we've
145        * completed.
146        */
147       TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + towrite);
148     }
149   }
150 
151   /* Now we check the input VIO to see if there is data left to
152    * read.
153    */
154   if (TSVIONTodoGet(input_vio) > 0) {
155     if (towrite > 0) {
156       /* If there is data left to read, then we reenable the output
157        * connection by reenabling the output VIO. This will wake up
158        * the output connection and allow it to consume data from the
159        * output buffer.
160        */
161       TSVIOReenable(data->output_vio);
162 
163       /* Call back the input VIO continuation to let it know that we
164        * are ready for more data.
165        */
166       TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
167     }
168   } else {
169     /* If there is no data left to read, then we modify the output
170      * VIO to reflect how much data the output connection should
171      * expect. This allows the output connection to know when it
172      * is done reading. We then reenable the output connection so
173      * that it can consume the data we just gave it.
174      */
175     TSVIONBytesSet(data->output_vio, TSVIONDoneGet(input_vio));
176     TSVIOReenable(data->output_vio);
177 
178     /* Call back the input VIO continuation to let it know that we
179      * have completed the write operation.
180      */
181     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
182   }
183 }
184 
185 static int
null_transform(TSCont contp,TSEvent event,void * edata)186 null_transform(TSCont contp, TSEvent event, void *edata)
187 {
188   /* Check to see if the transformation has been closed by a call to
189    * TSVConnClose.
190    */
191   TSDebug(PLUGIN_NAME, "Entering null_transform()");
192 
193   if (TSVConnClosedGet(contp)) {
194     TSDebug(PLUGIN_NAME, "\tVConn is closed");
195     my_data_destroy(TSContDataGet(contp));
196     TSContDestroy(contp);
197     return 0;
198   } else {
199     switch (event) {
200     case TS_EVENT_ERROR: {
201       TSVIO input_vio;
202 
203       TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_ERROR");
204       /* Get the write VIO for the write operation that was
205        * performed on ourself. This VIO contains the continuation of
206        * our parent transformation. This is the input VIO.
207        */
208       input_vio = TSVConnWriteVIOGet(contp);
209 
210       /* Call back the write VIO continuation to let it know that we
211        * have completed the write operation.
212        */
213       TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
214     } break;
215     case TS_EVENT_VCONN_WRITE_COMPLETE:
216       TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_VCONN_WRITE_COMPLETE");
217       /* When our output connection says that it has finished
218        * reading all the data we've written to it then we should
219        * shutdown the write portion of its connection to
220        * indicate that we don't want to hear about it anymore.
221        */
222       TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
223       break;
224 
225     /* If we get a WRITE_READY event or any other type of
226      * event (sent, perhaps, because we were re-enabled) then
227      * we'll attempt to transform more data.
228      */
229     case TS_EVENT_VCONN_WRITE_READY:
230       TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_VCONN_WRITE_READY");
231       handle_transform(contp);
232       break;
233     default:
234       TSDebug(PLUGIN_NAME, "\t(event is %d)", event);
235       handle_transform(contp);
236       break;
237     }
238   }
239 
240   return 0;
241 }
242 
243 static int
transformable(TSHttpTxn txnp)244 transformable(TSHttpTxn txnp)
245 {
246   /*
247    *  We are only interested in transforming "200 OK" responses.
248    */
249 
250   TSMBuffer bufp;
251   TSMLoc hdr_loc;
252   TSHttpStatus resp_status;
253   int retv = 0;
254 
255   TSDebug(PLUGIN_NAME, "Entering transformable()");
256 
257   if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc)) {
258     resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
259     retv        = (resp_status == TS_HTTP_STATUS_OK);
260     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
261   }
262 
263   TSDebug(PLUGIN_NAME, "Exiting transformable with return %d", retv);
264   return retv;
265 }
266 
267 static void
transform_add(TSHttpTxn txnp)268 transform_add(TSHttpTxn txnp)
269 {
270   TSVConn connp;
271 
272   TSDebug(PLUGIN_NAME, "Entering transform_add()");
273   connp = TSTransformCreate(null_transform, txnp);
274   TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
275 }
276 
277 static int
transform_plugin(TSCont contp,TSEvent event,void * edata)278 transform_plugin(TSCont contp, TSEvent event, void *edata)
279 {
280   TSHttpTxn txnp = (TSHttpTxn)edata;
281 
282   TSDebug(PLUGIN_NAME, "Entering transform_plugin()");
283   switch (event) {
284   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
285     TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_HTTP_READ_RESPONSE_HDR");
286     if (transformable(txnp)) {
287       transform_add(txnp);
288     }
289 
290     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
291     return 0;
292   default:
293     break;
294   }
295 
296   return 0;
297 }
298 
299 void
TSPluginInit(int argc,const char * argv[])300 TSPluginInit(int argc, const char *argv[])
301 {
302   TSPluginRegistrationInfo info;
303 
304   info.plugin_name   = PLUGIN_NAME;
305   info.vendor_name   = "Apache Software Foundation";
306   info.support_email = "dev@trafficserver.apache.org";
307 
308   if (TSPluginRegister(&info) != TS_SUCCESS) {
309     TSError("[%s] Plugin registration failed", PLUGIN_NAME);
310 
311     goto Lerror;
312   }
313 
314   TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(transform_plugin, NULL));
315   return;
316 
317 Lerror:
318   TSError("[%s] Unable to initialize plugin (disabled)", PLUGIN_NAME);
319 }
320