1 /** @file
2 
3   Multiplexes request to other origins.
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 #include <algorithm>
24 #include <ts/ts.h>
25 #include <ts/remap.h>
26 
27 #include <cinttypes>
28 
29 #include "dispatch.h"
30 #include "fetcher.h"
31 #include "original-request.h"
32 #include "post.h"
33 
34 #ifndef PLUGIN_TAG
35 #error Please define a PLUGIN_TAG before including this file.
36 #endif
37 
38 // 1s
39 const size_t DEFAULT_TIMEOUT = 1000000000000;
40 
41 Statistics statistics;
42 
43 TSReturnCode
TSRemapInit(TSRemapInterface *,char *,int)44 TSRemapInit(TSRemapInterface *, char *, int)
45 {
46   {
47     timeout                      = 0;
48     const char *const timeoutEnv = getenv(PLUGIN_TAG "__timeout");
49     if (timeoutEnv != nullptr) {
50       timeout = atol(timeoutEnv);
51     }
52     if (timeout < 1) {
53       timeout = DEFAULT_TIMEOUT;
54     }
55     TSDebug(PLUGIN_TAG, "timeout is set to: %zu", timeout);
56   }
57 
58   statistics.failures = TSStatCreate(PLUGIN_TAG ".failures", TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
59 
60   statistics.hits = TSStatCreate(PLUGIN_TAG ".hits", TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
61 
62   statistics.time = TSStatCreate(PLUGIN_TAG ".time", TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_AVG);
63 
64   statistics.requests = TSStatCreate(PLUGIN_TAG ".requests", TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
65 
66   statistics.timeouts = TSStatCreate(PLUGIN_TAG ".timeouts", TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
67 
68   statistics.size = TSStatCreate(PLUGIN_TAG ".size", TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_AVG);
69 
70   return TS_SUCCESS;
71 }
72 
73 TSReturnCode
TSRemapNewInstance(int argc,char ** argv,void ** i,char *,int)74 TSRemapNewInstance(int argc, char **argv, void **i, char *, int)
75 {
76   assert(i != nullptr);
77   Instance *instance    = new Instance;
78   instance->skipPostPut = false;
79 
80   if (argc > 2) {
81     std::copy_if(argv + 2, argv + argc, std::back_inserter(instance->origins), [&](const std::string &s) {
82       if (s == "proxy.config.multiplexer.skip_post_put=1") {
83         instance->skipPostPut = true;
84         return false;
85       }
86       return true;
87     });
88   }
89   TSDebug(PLUGIN_TAG, "skipPostPut is %s", (instance->skipPostPut ? "true" : "false"));
90 
91   *i = static_cast<void *>(instance);
92 
93   return TS_SUCCESS;
94 }
95 
96 void
TSRemapDeleteInstance(void * i)97 TSRemapDeleteInstance(void *i)
98 {
99   assert(i != nullptr);
100   delete static_cast<Instance *>(i);
101 }
102 
103 void
DoRemap(const Instance & i,TSHttpTxn t)104 DoRemap(const Instance &i, TSHttpTxn t)
105 {
106   assert(t != nullptr);
107   /*
108   if (POST || PUT) {
109     transformRequest
110   }
111   */
112   TSMBuffer buffer;
113   TSMLoc location;
114 
115   CHECK(TSHttpTxnClientReqGet(t, &buffer, &location));
116 
117   assert(buffer != nullptr);
118   assert(location != nullptr);
119 
120   int length;
121   const char *const method = TSHttpHdrMethodGet(buffer, location, &length);
122 
123   TSDebug(PLUGIN_TAG, "Method is %s.", std::string(method, length).c_str());
124 
125   if (i.skipPostPut && ((length == TS_HTTP_LEN_POST && memcmp(TS_HTTP_METHOD_POST, method, TS_HTTP_LEN_POST) == 0) ||
126                         (length == TS_HTTP_LEN_PUT && memcmp(TS_HTTP_METHOD_PUT, method, TS_HTTP_LEN_PUT) == 0))) {
127     TSHandleMLocRelease(buffer, TS_NULL_MLOC, location);
128   } else {
129     {
130       TSMLoc field;
131 
132       CHECK(TSMimeHdrFieldCreateNamed(buffer, location, "X-Multiplexer", 13, &field));
133       assert(field != nullptr);
134 
135       CHECK(TSMimeHdrFieldValueStringSet(buffer, location, field, -1, "original", 8));
136 
137       CHECK(TSMimeHdrFieldAppend(buffer, location, field));
138 
139       CHECK(TSHandleMLocRelease(buffer, location, field));
140     }
141 
142     Requests requests;
143     generateRequests(i.origins, buffer, location, requests);
144     assert(requests.size() == i.origins.size());
145 
146     if (length == TS_HTTP_LEN_POST && memcmp(TS_HTTP_METHOD_POST, method, TS_HTTP_LEN_POST) == 0) {
147       const TSVConn vconnection = TSTransformCreate(handlePost, t);
148       assert(vconnection != nullptr);
149       TSContDataSet(vconnection, new PostState(requests));
150       assert(requests.empty());
151       TSHttpTxnHookAdd(t, TS_HTTP_REQUEST_TRANSFORM_HOOK, vconnection);
152     } else {
153       dispatch(requests, timeout);
154     }
155 
156     TSHandleMLocRelease(buffer, TS_NULL_MLOC, location);
157 
158     TSStatIntIncrement(statistics.requests, 1);
159   }
160 }
161 
162 TSRemapStatus
TSRemapDoRemap(void * i,TSHttpTxn t,TSRemapRequestInfo * r)163 TSRemapDoRemap(void *i, TSHttpTxn t, TSRemapRequestInfo *r)
164 {
165   assert(i != nullptr);
166   assert(t != nullptr);
167   const Instance *const instance = static_cast<const Instance *>(i);
168 
169   if (!instance->origins.empty() && !TSHttpTxnIsInternal(t)) {
170     DoRemap(*instance, t);
171   } else {
172     TSDebug(PLUGIN_TAG, "Skipping transaction %p", t);
173   }
174 
175   return TSREMAP_NO_REMAP;
176 }
177