blob: 08be6b95f283109af14438cf7b057a995dca491c [file] [log] [blame]
// Copyright 2010 Google Inc.
//
// Licensed 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.
//
// Author: lsong@google.com (Libo Song)
// jmarantz@google.com (Joshua Marantz)
//
// The Apache handler for rewritten resources and a couple other Apache hooks.
#ifndef PAGESPEED_APACHE_INSTAWEB_HANDLER_H_
#define PAGESPEED_APACHE_INSTAWEB_HANDLER_H_
#include "pagespeed/apache/apache_fetch.h"
#include "net/instaweb/http/public/request_context.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_query.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/http/query_params.h"
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "apr_pools.h" // for apr_status_t
// The httpd header must be after the instaweb_context.h. Otherwise,
// the compiler will complain
// "strtoul_is_not_a_portable_function_use_strtol_instead".
#include "pagespeed/apache/apache_httpd_includes.h"
namespace net_instaweb {
class ApacheConfig;
class ApacheRequestContext;
class ApacheRewriteDriverFactory;
class ApacheServerContext;
class InPlaceResourceRecorder;
// Context for handling a request, computing options and request headers in
// the constructor.
//
// TODO(jmarantz): There are many static methods in this class. Some
// of them need to stay that way as they are used as C entry points.
// Some are helper methods, and we could adopt a policy of requiring
// the static methods to instantiate the class (possibly making its
// constructor lighter weight) and then calling them explicitly. For
// the time being, we'll leave these static methods with the
// non-compliant lower_case_names_with_underscores naming convention
// and fix their naming when we update whether they should be static
// or not.
class InstawebHandler {
public:
explicit InstawebHandler(request_rec* request);
~InstawebHandler();
// Any PageSpeed query params are removed.
const GoogleUrl& stripped_gurl() const { return stripped_gurl_; }
const RequestContextPtr request_context() const { return request_context_; }
bool use_custom_options() const { return custom_options_.get() != NULL; }
const QueryParams& query_params() { return rewrite_query_.query_params(); }
const QueryParams& pagespeed_query_params() {
return rewrite_query_.pagespeed_query_params();
}
const QueryParams& pagespeed_option_cookies() {
return rewrite_query_.pagespeed_option_cookies();
}
void RemoveStrippedResponseHeadersFromApacheRequest();
// Makes a driver from the request_context and options. Note that
// this can only be called once, as it potentially mutates the options
// as it transfers ownership of custom_options. The driver is owned by
// the InstawebHandler and will be cleaned up at destruction, unless you
// call DisownDriver().
RewriteDriver* MakeDriver();
// Prevent "this" from cleaning up rewrite_driver_ at destruction.
void DisownDriver();
// Allocates a Fetch object associated with the current request and
// the specified URL. Include in debug_info anything that's cheap to create
// and would be informative if something went wrong with the fetch.
// If any uses will be from other threads you must set buffered=true to keep
// your other thread from getting blocked if our output is being read by a
// slow reader.
ApacheFetch* MakeFetch(
const GoogleString& url, bool buffered, StringPiece debug_info);
// Allocates a Fetch object associated with the current request and its URL.
// Please read the comment above before setting buffered=false.
ApacheFetch* MakeFetch(bool buffered, StringPiece debug_info) {
return MakeFetch(original_url_, buffered, debug_info);
}
// Attempts to handle this as a proxied resource (see
// MapProxyDomain). Returns false if the proxy handling didn't
// occur, and another handler should take over the request.
bool HandleAsProxy();
// Tries to acts as a full-featured proxy, handling both HTML and
// resources.
void HandleAsProxyForAll();
// Attempts to handle this as an in-place resource. Returns false if
// the in-place handling didn't occur, and another handler should take
// over the request.
bool HandleAsInPlace();
// Unconditionally handles a resource that looks like a .pagespeed. resource,
// whether the result is success or failure.
void HandleAsPagespeedResource();
// Waits for an outstanding fetch (obtained by MakeFetch) to complete. On
// failure, a failure response will be sent to the client. The request is
// handled unconditionally.
void WaitForFetch();
// Loads the URL based on the fetchers and other infrastructure in the
// factory, returning true if the request was handled. This is used
// both for slurping and for handling URLs ending with proxy_suffix.
bool ProxyUrl();
// Checks to see whether the configuration has set up cookie-based
// proxy authentication. If so, and the cookies are not present,
// clients will be redirected to a page where the cookies can be
// obtained. Returns true if the client is authorized for proxying.
// Return false and responds to the request_ if the client was not
// authorized.
bool AuthenticateProxy();
RequestHeaders* ReleaseRequestHeaders() { return request_headers_.release(); }
// Returns the options, whether they were custom-computed due to htaccess
// file, query params, or headers, or were the default options for the vhost.
const ApacheConfig* options() { return options_; }
// Was this request made by mod_pagespeed itself? If so, we should not try to
// handle it, just let Apache deal with it like normal.
static bool is_pagespeed_subrequest(request_rec* request);
// Handle mod_pagespeed-specific requests. Handles both .pagespeed. rewritten
// resources and /mod_pagespeed_statistics, /mod_pagespeed_beacon, etc.
static apr_status_t instaweb_handler(request_rec* request);
// Save the original URL as a request "note" before mod_rewrite has
// a chance to corrupt mod_pagespeed's generated URLs, which would
// prevent instaweb_handler from being able to decode the resource.
static apr_status_t save_url_hook(request_rec *request);
// Implementation of the Apache 'translate_name' hook. Used by the actual hook
// 'save_url_hook' and directly when we already have the server context.
static apr_status_t save_url_in_note(request_rec *request,
ApacheServerContext* server_context);
// By default, apache imposes limitations on URL segments of around
// 256 characters that appear to correspond to filename limitations.
// To prevent that, we hook map_to_storage for our own purposes.
static apr_status_t instaweb_map_to_storage(request_rec* request);
// This must be called on any InPlaceResourceRecorder allocated by
// instaweb_handler before calling DoneAndSetHeaders() on it.
static void AboutToBeDoneWithRecorder(request_rec* request,
InPlaceResourceRecorder* recorder);
private:
// Evaluate custom_options based upon global_options, directory-specific
// options and query-param/request-header options. Stores computed options
// in custom_options_ if needed. Sets options_ to point to the correct
// options to use.
void ComputeCustomOptions();
static bool IsCompressibleContentType(const char* content_type);
static void send_out_headers_and_body(
request_rec* request,
const ResponseHeaders& response_headers,
const GoogleString& output);
// Determines whether the url can be handled as a mod_pagespeed or in-place
// optimized resource, and handles it, returning true. Success status is
// written to the status code in the response headers.
static bool handle_as_resource(ApacheServerContext* server_context,
request_rec* request,
GoogleUrl* gurl);
// Write response headers and send out headers and output, including
// the option for a custom Content-Type.
static void write_handler_response(const StringPiece& output,
request_rec* request,
ContentType content_type,
const StringPiece& cache_control);
static void write_handler_response(const StringPiece& output,
request_rec* request);
// Returns request URL if it was a .pagespeed. rewritten resource URL.
// Otherwise returns NULL. Since other Apache modules can change request->uri,
// we stow the original request URL in a note. This method reads that note
// and thus should return the URL that the browser actually requested (rather
// than a mod_rewrite altered URL).
static const char* get_instaweb_resource_url(
request_rec* request, ApacheServerContext* server_context);
// Helper function to support the LogRequestHeadersHandler. Called
// once for each header to write header data in a form suitable for
// javascript inlining. Used only for tests.
static int log_request_headers(void* logging_data, const char* key,
const char* value);
static void instaweb_static_handler(request_rec* request,
ApacheServerContext* server_context);
static apr_status_t instaweb_statistics_handler(
request_rec* request, ApacheServerContext* server_context,
ApacheRewriteDriverFactory* factory);
// Append the query params from a request into data. This just
// parses the query params from a request URL. For parsing the query
// params from a POST body, use parse_body_from_post(). Return true
// if successful, otherwise, returns false and sets ret to the
// appropriate status.
static bool parse_query_params(const request_rec* request, GoogleString* data,
apr_status_t* ret);
// Read the body from a POST request and append to data. Return true
// if successful, otherwise, returns false and sets ret to the
// appropriate status.
static bool parse_body_from_post(const request_rec* request,
GoogleString* data, apr_status_t* ret);
static apr_status_t instaweb_beacon_handler(
request_rec* request, ApacheServerContext* server_context);
static bool IsBeaconUrl(const RewriteOptions::BeaconUrl& beacons,
const GoogleUrl& gurl);
request_rec* request_;
RequestContextPtr request_context_;
ApacheRequestContext* apache_request_context_; // owned by request_context_.
ApacheServerContext* server_context_;
scoped_ptr<RequestHeaders> request_headers_;
scoped_ptr<ResponseHeaders> response_headers_;
GoogleString original_url_;
GoogleUrl stripped_gurl_; // Any PageSpeed query params are removed.
scoped_ptr<ApacheConfig> custom_options_;
// These options_ can be in one of three states:
// - they can point to the config's global_options
// - they can point to the custom_options_
// - after driver creation, they can point to the rewrite_driver_->options()
// Thus this set of options is not owned by this class.
//
// In all three of these states, the pointer and semantics will always be
// the same. Only the ownership changes.
const ApacheConfig* options_;
RewriteDriver* rewrite_driver_;
bool driver_owned_;
int num_response_attributes_;
RewriteQuery rewrite_query_;
ApacheFetch* fetch_;
DISALLOW_COPY_AND_ASSIGN(InstawebHandler);
};
} // namespace net_instaweb
#endif // PAGESPEED_APACHE_INSTAWEB_HANDLER_H_