File: GzipTransformationPlugin.cc

package info (click to toggle)
trafficserver 9.2.5%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 53,008 kB
  • sloc: cpp: 345,484; ansic: 31,134; python: 24,200; sh: 7,271; makefile: 3,045; perl: 2,261; java: 277; pascal: 119; sql: 94; xml: 2
file content (206 lines) | stat: -rw-r--r-- 7,606 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
  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 <iostream>
#include <string_view>
#include "tscpp/api/GlobalPlugin.h"
#include "tscpp/api/TransactionPlugin.h"
#include "tscpp/api/TransformationPlugin.h"
#include "tscpp/api/GzipInflateTransformation.h"
#include "tscpp/api/GzipDeflateTransformation.h"
#include "tscpp/api/PluginInit.h"
#include "tscpp/api/Logger.h"

using namespace atscppapi;
using namespace atscppapi::transformations;
using std::string;

#define TAG "gzip_transformation"

namespace
{
GlobalPlugin *plugin;
}

/*
 * Note, the GzipInflateTransformation and GzipDeflateTransformation do not
 * check headers to determine if the content was gzipped and it doesn't check
 * headers to make sure the client supports gzip, this is entirely up to the plugin
 * to verify that the content-encoding is gzipped, it's also up to the client
 * to make sure the user's accept-encoding supports gzip.
 *
 * Read this example very carefully, in this example we modify the Accept-Encoding
 * header to the origin to ensure that we will be returned gzipped or identity encoding
 * content. If we receive gzipped content we will inflate it, we will apply our transformation
 * and if the client supports gzip we will deflate the content that we transformed. Finally,
 * if the user supported gzip (then they got gzip) and we must make sure the content-encoding
 * header is correctly set on the way out.
 */

class Helpers
{
public:
  static bool
  clientAcceptsGzip(Transaction &transaction)
  {
    return transaction.getClientRequest().getHeaders().values("Accept-Encoding").find("gzip") != string::npos;
  }

  static bool
  serverReturnedGzip(Transaction &transaction)
  {
    return transaction.getServerResponse().getHeaders().values("Content-Encoding").find("gzip") != string::npos;
  }

  enum ContentType {
    UNKNOWN    = 0,
    TEXT_HTML  = 1,
    TEXT_PLAIN = 2,
  };

  static ContentType
  getContentType(Transaction &transaction)
  {
    if (transaction.getServerResponse().getHeaders().values("Content-Type").find("text/html") != string::npos) {
      return TEXT_HTML;
    } else if (transaction.getServerResponse().getHeaders().values("Content-Type").find("text/plain") != string::npos) {
      return TEXT_PLAIN;
    } else {
      return UNKNOWN;
    }
  }
};

class SomeTransformationPlugin : public TransformationPlugin
{
public:
  explicit SomeTransformationPlugin(Transaction &transaction)
    : TransformationPlugin(transaction, RESPONSE_TRANSFORMATION), transaction_(transaction)
  {
    registerHook(HOOK_SEND_RESPONSE_HEADERS);
  }

  void
  handleSendResponseHeaders(Transaction &transaction) override
  {
    TS_DEBUG(TAG, "Added X-Content-Transformed header");
    transaction.getClientResponse().getHeaders()["X-Content-Transformed"] = "1";
    transaction.resume();
  }

  void
  consume(std::string_view data) override
  {
    produce(data);
  }

  void
  handleInputComplete() override
  {
    Helpers::ContentType content_type = Helpers::getContentType(transaction_);
    if (content_type == Helpers::TEXT_HTML) {
      TS_DEBUG(TAG, "Adding an HTML comment at the end of the page");
      produce("\n<br /><!-- Gzip Transformation Plugin Was Here -->");
    } else if (content_type == Helpers::TEXT_PLAIN) {
      TS_DEBUG(TAG, "Adding a text comment at the end of the page");
      produce("\nGzip Transformation Plugin Was Here");
    } else {
      TS_DEBUG(TAG, "Unable to add TEXT or HTML comment because content type was not text/html or text/plain.");
    }
    setOutputComplete();
  }

  ~SomeTransformationPlugin() override {}

private:
  Transaction &transaction_;
};

class GlobalHookPlugin : public GlobalPlugin
{
public:
  GlobalHookPlugin()
  {
    registerHook(HOOK_SEND_REQUEST_HEADERS);
    registerHook(HOOK_READ_RESPONSE_HEADERS);
    registerHook(HOOK_SEND_RESPONSE_HEADERS);
  }

  void
  handleSendRequestHeaders(Transaction &transaction) override
  {
    // Since we can only decompress gzip we will change the accept encoding header
    // to gzip, even if the user cannot accept gzipped content we will return to them
    // uncompressed content in that case since we have to be able to transform the content.
    string original_accept_encoding = transaction.getServerRequest().getHeaders().values("Accept-Encoding");

    // Make sure it's done on the server request to avoid clobbering the clients original accept encoding header.
    transaction.getServerRequest().getHeaders()["Accept-Encoding"] = "gzip";
    TS_DEBUG(TAG, "Changed the servers request accept encoding header from \"%s\" to gzip", original_accept_encoding.c_str());

    transaction.resume();
  }

  void
  handleReadResponseHeaders(Transaction &transaction) override
  {
    TS_DEBUG(TAG, "Determining if we need to add an inflate transformation or a deflate transformation..");
    // We're guaranteed to have been returned either gzipped content or Identity.

    if (Helpers::serverReturnedGzip(transaction)) {
      // If the returned content was gzipped we will inflate it so we can transform it.
      TS_DEBUG(TAG, "Creating Inflate Transformation because the server returned gzipped content");
      transaction.addPlugin(new GzipInflateTransformation(transaction, TransformationPlugin::RESPONSE_TRANSFORMATION));
    }

    transaction.addPlugin(new SomeTransformationPlugin(transaction));

    // Even if the server didn't return gzipped content, if the user supports it we will gzip it.
    if (Helpers::clientAcceptsGzip(transaction)) {
      TS_DEBUG(TAG, "The client supports gzip so we will deflate the content on the way out.");
      transaction.addPlugin(new GzipDeflateTransformation(transaction, TransformationPlugin::RESPONSE_TRANSFORMATION));
    }
    transaction.resume();
  }

  void
  handleSendResponseHeaders(Transaction &transaction) override
  {
    // If the client supported gzip then we can guarantee they are receiving gzip since regardless of the
    // origins content-encoding we returned gzip, so let's make sure the content-encoding header is correctly
    // set to gzip or identity.
    if (Helpers::clientAcceptsGzip(transaction)) {
      TS_DEBUG(TAG, "Setting the client response content-encoding to gzip since the user supported it, that's what they got.");
      transaction.getClientResponse().getHeaders()["Content-Encoding"] = "gzip";
    } else {
      TS_DEBUG(TAG, "Setting the client response content-encoding to identity since the user didn't support gzip");
      transaction.getClientResponse().getHeaders()["Content-Encoding"] = "identity";
    }
    transaction.resume();
  }
};

void
TSPluginInit(int argc ATSCPPAPI_UNUSED, const char *argv[] ATSCPPAPI_UNUSED)
{
  if (!RegisterGlobalPlugin("CPP_Example_GzipTransformation", "apache", "dev@trafficserver.apache.org")) {
    return;
  }
  TS_DEBUG(TAG, "TSPluginInit");
  plugin = new GlobalHookPlugin();
}