Merge pull request #190 from pagespeed/oschaaf-inflate-upstream

Adds suport for silently inflating compressed upstreams. This is
good for when the link to the upstream is low bandwidth

This should fix https://github.com/pagespeed/ngx_pagespeed/issues/140
This commit is contained in:
Otto van der Schaaf
2013-03-20 12:24:51 -07:00
+97 -4
View File
@@ -53,11 +53,13 @@ extern "C" {
#include "net/instaweb/public/version.h"
#include "net/instaweb/util/public/google_message_handler.h"
#include "net/instaweb/util/public/google_url.h"
#include "net/instaweb/util/public/gzip_inflater.h"
#include "pthread_shared_mem.h"
#include "net/instaweb/util/public/query_params.h"
#include "net/instaweb/util/public/string.h"
#include "net/instaweb/util/public/string_writer.h"
#include "net/instaweb/util/public/time_util.h"
#include "net/instaweb/util/stack_buffer.h"
extern ngx_module_t ngx_pagespeed;
@@ -291,6 +293,7 @@ typedef struct {
bool is_resource_fetch;
bool sent_headers;
bool write_pending;
net_instaweb::GzipInflater* inflater_;
} ps_request_ctx_t;
ngx_int_t ps_body_filter(ngx_http_request_t* r, ngx_chain_t* in);
@@ -722,6 +725,11 @@ void ps_release_request_context(void* data) {
ctx->base_fetch = NULL;
}
if (ctx->inflater_ != NULL) {
delete ctx->inflater_;
ctx->inflater_ = NULL;
}
// Close the connection, delete the events attached with it, and free it to
// Nginx's connection pool
if (ctx->pagespeed_connection != NULL) {
@@ -1329,10 +1337,26 @@ void ps_send_to_pagespeed(ngx_http_request_t* r,
cur->buf->last_buf = 0;
CHECK(ctx->proxy_fetch != NULL);
ctx->proxy_fetch->Write(StringPiece(reinterpret_cast<char*>(cur->buf->pos),
cur->buf->last - cur->buf->pos),
cfg_s->handler);
if (ctx->inflater_ == NULL) {
ctx->proxy_fetch->Write(
StringPiece(reinterpret_cast<char*>(cur->buf->pos),
cur->buf->last - cur->buf->pos), cfg_s->handler);
} else {
char buf[net_instaweb::kStackBufferSize];
ctx->inflater_->SetInput(reinterpret_cast<char*>(cur->buf->pos),
cur->buf->last - cur->buf->pos);
while (ctx->inflater_->HasUnconsumedInput()) {
int num_inflated_bytes = ctx->inflater_->InflateBytes(
buf, net_instaweb::kStackBufferSize);
if (num_inflated_bytes < 0) {
cfg_s->handler->Message(net_instaweb::kWarning,
"Corrupted inflation");
} else if (num_inflated_bytes > 0) {
ctx->proxy_fetch->Write(StringPiece(buf, num_inflated_bytes),
cfg_s->handler);
}
}
}
// We're done with buffers as we pass them through, so mark them as sent as
// we go.
cur->buf->pos = cur->buf->last;
@@ -1468,6 +1492,46 @@ void ps_strip_html_headers(ngx_http_request_t* r) {
}
}
// Returns true, if the the response headers indicate there are multiple
// content encodings.
bool ps_has_stacked_content_encoding(ngx_http_request_t* r) {
ngx_uint_t i;
ngx_list_part_t* part = &(r->headers_out.headers.part);
ngx_table_elt_t* header = static_cast<ngx_table_elt_t*>(part->elts);
int field_count = 0;
for (i = 0 ; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = static_cast<ngx_table_elt_t*>(part->elts);
i = 0;
}
// Inspect Content-Encoding headers, checking all value fields
// If an origin returns gzip,foo, that is what we will get here.
if (STR_CASE_EQ_LITERAL(header[i].key, "Content-Encoding")) {
if (header[i].value.data != NULL && header[i].value.len > 0) {
char* p = reinterpret_cast<char*>(header[i].value.data);
ngx_uint_t j;
for (j = 0; j < header[i].value.len; j++) {
if (p[j] == ',' || j == header[i].value.len - 1) {
field_count++;
}
}
if (field_count > 1) {
return true;
}
}
}
}
return false;
}
ngx_int_t ps_header_filter(ngx_http_request_t* r) {
ps_srv_conf_t* cfg_s = ps_get_srv_config(r);
if (cfg_s->server_context == NULL) {
@@ -1521,6 +1585,35 @@ ngx_int_t ps_header_filter(ngx_http_request_t* r) {
}
ctx = ps_get_request_context(r);
CHECK(ctx->driver != NULL); // Not a resource fetch, so driver is defined.
if (r->headers_out.content_encoding &&
r->headers_out.content_encoding->value.len) {
// headers_out.content_encoding will be set to the exact last
// Content-Encoding response header value that nginx receives. To
// check if there were multiple (aka stacked) encodings in the
// response headers, we must iterate them all.
if (!ps_has_stacked_content_encoding(r)) {
StringPiece content_encoding =
str_to_string_piece(r->headers_out.content_encoding->value);
net_instaweb::GzipInflater::InflateType inflate_type;
bool is_encoded = false;
if (net_instaweb::StringCaseEqual(content_encoding, "deflate")) {
is_encoded = true;
inflate_type = net_instaweb::GzipInflater::kDeflate;
} else if (net_instaweb::StringCaseEqual(content_encoding, "gzip")) {
is_encoded = true;
inflate_type = net_instaweb::GzipInflater::kGzip;
}
if (is_encoded) {
r->headers_out.content_encoding->hash = 0;
r->headers_out.content_encoding = NULL;
ctx->inflater_ = new net_instaweb::GzipInflater(inflate_type);
ctx->inflater_->Init();
}
}
}
const net_instaweb::RewriteOptions* options = ctx->driver->options();
ps_strip_html_headers(r);