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 
71   TSDebug(PLUGIN_NAME, "Entering handle_transform()");
72   /* Get the output (downstream) vconnection where we'll write data to. */
73 
74   output_conn = TSTransformOutputVConnGet(contp);
75 
76   /* Get the write VIO for the write operation that was performed on
77    * ourself. This VIO contains the buffer that we are to read from
78    * as well as the continuation we are to call when the buffer is
79    * empty. This is the input VIO (the write VIO for the upstream
80    * vconnection).
81    */
82   input_vio = TSVConnWriteVIOGet(contp);
83 
84   /* Get our data structure for this operation. The private data
85    * structure contains the output VIO and output buffer. If the
86    * private data structure pointer is NULL, then we'll create it
87    * and initialize its internals.
88    */
89   data = TSContDataGet(contp);
90   if (!data) {
91     data                = my_data_alloc();
92     data->output_buffer = TSIOBufferCreate();
93     data->output_reader = TSIOBufferReaderAlloc(data->output_buffer);
94     TSDebug(PLUGIN_NAME, "\tWriting %" PRId64 " bytes on VConn", TSVIONBytesGet(input_vio));
95     // data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, INT32_MAX);
96     data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, INT64_MAX);
97     // data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, TSVIONBytesGet(input_vio));
98     TSContDataSet(contp, data);
99   }
100 
101   /* We also check to see if the input VIO's buffer is non-NULL. A
102    * NULL buffer indicates that the write operation has been
103    * shutdown and that the upstream continuation does not want us to send any
104    * more WRITE_READY or WRITE_COMPLETE events. For this simplistic
105    * transformation that means we're done. In a more complex
106    * transformation we might have to finish writing the transformed
107    * data to our output connection.
108    */
109   buf_test = TSVIOBufferGet(input_vio);
110 
111   if (!buf_test) {
112     TSVIONBytesSet(data->output_vio, TSVIONDoneGet(input_vio));
113     TSVIOReenable(data->output_vio);
114     return;
115   }
116 
117   /* Determine how much data we have left to read. For this null
118    * transform plugin this is also the amount of data we have left
119    * to write to the output connection.
120    */
121   towrite = TSVIONTodoGet(input_vio);
122   TSDebug(PLUGIN_NAME, "\ttoWrite is %" PRId64 "", towrite);
123 
124   if (towrite > 0) {
125     /* The amount of data left to read needs to be truncated by
126      * the amount of data actually in the read buffer.
127      */
128     int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(input_vio));
129     TSDebug(PLUGIN_NAME, "\tavail is %" PRId64 "", avail);
130     if (towrite > avail) {
131       towrite = avail;
132     }
133 
134     if (towrite > 0) {
135       /* Copy the data from the read buffer to the output buffer. */
136       TSIOBufferCopy(TSVIOBufferGet(data->output_vio), TSVIOReaderGet(input_vio), towrite, 0);
137 
138       /* Tell the read buffer that we have read the data and are no
139        * longer interested in it.
140        */
141       TSIOBufferReaderConsume(TSVIOReaderGet(input_vio), towrite);
142 
143       /* Modify the input VIO to reflect how much data we've
144        * completed.
145        */
146       TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + towrite);
147     }
148   }
149 
150   /* Now we check the input VIO to see if there is data left to
151    * read.
152    */
153   if (TSVIONTodoGet(input_vio) > 0) {
154     if (towrite > 0) {
155       /* If there is data left to read, then we reenable the output
156        * connection by reenabling the output VIO. This will wake up
157        * the output connection and allow it to consume data from the
158        * output buffer.
159        */
160       TSVIOReenable(data->output_vio);
161 
162       /* Call back the input VIO continuation to let it know that we
163        * are ready for more data.
164        */
165       TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
166     }
167   } else {
168     /* If there is no data left to read, then we modify the output
169      * VIO to reflect how much data the output connection should
170      * expect. This allows the output connection to know when it
171      * is done reading. We then reenable the output connection so
172      * that it can consume the data we just gave it.
173      */
174     TSVIONBytesSet(data->output_vio, TSVIONDoneGet(input_vio));
175     TSVIOReenable(data->output_vio);
176 
177     /* Call back the input VIO continuation to let it know that we
178      * have completed the write operation.
179      */
180     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
181   }
182 }
183 
184 static int
null_transform(TSCont contp,TSEvent event,void * edata)185 null_transform(TSCont contp, TSEvent event, void *edata)
186 {
187   /* Check to see if the transformation has been closed by a call to
188    * TSVConnClose.
189    */
190   TSDebug(PLUGIN_NAME, "Entering null_transform()");
191 
192   if (TSVConnClosedGet(contp)) {
193     TSDebug(PLUGIN_NAME, "\tVConn is closed");
194     my_data_destroy(TSContDataGet(contp));
195     TSContDestroy(contp);
196     return 0;
197   } else {
198     switch (event) {
199     case TS_EVENT_ERROR: {
200       TSVIO input_vio;
201 
202       TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_ERROR");
203       /* Get the write VIO for the write operation that was
204        * performed on ourself. This VIO contains the continuation of
205        * our parent transformation. This is the input VIO.
206        */
207       input_vio = TSVConnWriteVIOGet(contp);
208 
209       /* Call back the write VIO continuation to let it know that we
210        * have completed the write operation.
211        */
212       TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
213     } break;
214     case TS_EVENT_VCONN_WRITE_COMPLETE:
215       TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_VCONN_WRITE_COMPLETE");
216       /* When our output connection says that it has finished
217        * reading all the data we've written to it then we should
218        * shutdown the write portion of its connection to
219        * indicate that we don't want to hear about it anymore.
220        */
221       TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
222       break;
223 
224     /* If we get a WRITE_READY event or any other type of
225      * event (sent, perhaps, because we were re-enabled) then
226      * we'll attempt to transform more data.
227      */
228     case TS_EVENT_VCONN_WRITE_READY:
229       TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_VCONN_WRITE_READY");
230       handle_transform(contp);
231       break;
232     default:
233       TSDebug(PLUGIN_NAME, "\t(event is %d)", event);
234       handle_transform(contp);
235       break;
236     }
237   }
238 
239   return 0;
240 }
241 
242 static int
transformable(TSHttpTxn txnp)243 transformable(TSHttpTxn txnp)
244 {
245   /*
246    *  We are only interested in transforming "200 OK" responses.
247    */
248 
249   TSMBuffer bufp;
250   TSMLoc hdr_loc;
251   TSHttpStatus resp_status;
252   int retv = 0;
253 
254   TSDebug(PLUGIN_NAME, "Entering transformable()");
255 
256   if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc)) {
257     resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
258     retv        = (resp_status == TS_HTTP_STATUS_OK);
259     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
260   }
261 
262   TSDebug(PLUGIN_NAME, "Exiting transformable with return %d", retv);
263   return retv;
264 }
265 
266 static void
transform_add(TSHttpTxn txnp)267 transform_add(TSHttpTxn txnp)
268 {
269   TSVConn connp;
270 
271   TSDebug(PLUGIN_NAME, "Entering transform_add()");
272   connp = TSTransformCreate(null_transform, txnp);
273   TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
274 }
275 
276 static int
transform_plugin(TSCont contp,TSEvent event,void * edata)277 transform_plugin(TSCont contp, TSEvent event, void *edata)
278 {
279   TSHttpTxn txnp = (TSHttpTxn)edata;
280 
281   TSDebug(PLUGIN_NAME, "Entering transform_plugin()");
282   switch (event) {
283   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
284     TSDebug(PLUGIN_NAME, "\tEvent is TS_EVENT_HTTP_READ_RESPONSE_HDR");
285     if (transformable(txnp)) {
286       transform_add(txnp);
287     }
288 
289     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
290     return 0;
291   default:
292     break;
293   }
294 
295   return 0;
296 }
297 
298 void
TSPluginInit(int argc,const char * argv[])299 TSPluginInit(int argc, const char *argv[])
300 {
301   TSPluginRegistrationInfo info;
302 
303   info.plugin_name   = PLUGIN_NAME;
304   info.vendor_name   = "Apache Software Foundation";
305   info.support_email = "dev@trafficserver.apache.org";
306 
307   if (TSPluginRegister(&info) != TS_SUCCESS) {
308     TSError("[%s] Plugin registration failed", PLUGIN_NAME);
309 
310     goto Lerror;
311   }
312 
313   TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(transform_plugin, NULL));
314   return;
315 
316 Lerror:
317   TSError("[%s] Unable to initialize plugin (disabled)", PLUGIN_NAME);
318 }
319