From 43a5ac968cbe95ecece9df41c7c31f75e60b5439 Mon Sep 17 00:00:00 2001 From: Graham Leggett Date: Sun, 14 Feb 2010 15:09:53 +0000 Subject: [PATCH] Introduce mod_reflector, a handler capable of reflecting POSTed request bodies back within the response through the output filter stack. Can be used to turn an output filter into a web service. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@910017 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 5 + docs/manual/filter.xml | 21 +++ docs/manual/mod/allmodules.xml | 1 + docs/manual/mod/mod_reflector.xml | 83 +++++++++++ modules/filters/config.m4 | 1 + modules/filters/mod_reflector.c | 231 ++++++++++++++++++++++++++++++ 6 files changed, 342 insertions(+) create mode 100644 docs/manual/mod/mod_reflector.xml create mode 100644 modules/filters/mod_reflector.c diff --git a/CHANGES b/CHANGES index a8c87f589c..78ac28e217 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,11 @@ Changes with Apache 2.3.7 + *) Introduce mod_reflector, a handler capable of reflecting POSTed + request bodies back within the response through the output filter + stack. Can be used to turn an output filter into a web service. + [Graham Leggett] + *) mod_proxy_http: Make sure that when an ErrorDocument is served from a reverse proxied URL, that the subrequest respects the status of the original request. This brings the behaviour of proxy_handler diff --git a/docs/manual/filter.xml b/docs/manual/filter.xml index 9c35c0c6ba..2e85f4d4ee 100644 --- a/docs/manual/filter.xml +++ b/docs/manual/filter.xml @@ -37,6 +37,7 @@ mod_ext_filter mod_include mod_charset_lite + mod_reflector FilterChain @@ -47,6 +48,7 @@ AddOutputFilter RemoveInputFilter RemoveOutputFilter + ReflectorHeader ExtFilterDefine ExtFilterOptions SetInputFilter @@ -118,6 +120,25 @@ document is not already in the desired charset +
+ +Exposing Filters as an HTTP Service +

Filters can be used to process content originating from the client in +addition to processing content originating on the server using the +mod_reflector module.

+ +

mod_reflector accepts POST requests from clients, and reflects +the content request body received within the POST request back in the response, +passing through the output filter stack on the way back to the client.

+ +

This technique can be used as an alternative to a web service running within +an application server stack, where an output filter provides the transformation +required on the request body. For example, the mod_deflate +module might be used to provide a general compression service, or an image +transformation filter might be turned into an image transformation service.

+ +
+
Using Filters

There are two ways to use filtering: Simple and Dynamic. diff --git a/docs/manual/mod/allmodules.xml b/docs/manual/mod/allmodules.xml index 39a51eef65..4edbb86bdd 100644 --- a/docs/manual/mod/allmodules.xml +++ b/docs/manual/mod/allmodules.xml @@ -75,6 +75,7 @@ mod_proxy_ftp.xml mod_proxy_http.xml mod_proxy_scgi.xml + mod_reflector.xml mod_remoteip.xml mod_reqtimeout.xml mod_request.xml diff --git a/docs/manual/mod/mod_reflector.xml b/docs/manual/mod/mod_reflector.xml new file mode 100644 index 0000000000..947a6f9baa --- /dev/null +++ b/docs/manual/mod/mod_reflector.xml @@ -0,0 +1,83 @@ + + + + + + + + + +mod_reflector +Reflect a request body as a response via the output filter stack. +Base +mod_reflector.c +reflector_module +Version 2.3 and later + +

+

This module allows request bodies to be reflected back to the + client, in the process passing the request through the output filter + stack. A suitably configured chain of filters can be used to transform + the request into a response. This module can used to turn an output + filter into an HTTP service.

+
+ +
Examples +
+
Compression service
+
Pass the request body through the DEFLATE filter to compress the + body. This request requires a Content-Encoding request header containing + "gzip" for the filter to return compressed data. + + <Location /compress>
+ SetHandler reflector
+ SetOutputFilter DEFLATE
+ </Location> +
+
+ +
Image downsampling service
+
Pass the request body through an image downsampling filter, and reflect + the results to the caller. + + <Location /downsample>
+ SetHandler reflector
+ SetOutputFilter DOWNSAMPLE
+ </Location> +
+
+
+
+ + +ReflectorHeader +Reflect an input header to the output headers +ReflectorHeader inputheader [outputheader] +server configvirtual host +directory.htaccess +Options + + +

This directive controls the reflection of request headers to the response. + The first argument is the name of the request header to copy. If the optional + second argument is specified, it will be used as the name of the response + header, otherwise the original request header name will be used.

+
+
+ + diff --git a/modules/filters/config.m4 b/modules/filters/config.m4 index 2bd171e5cf..3eccac2316 100644 --- a/modules/filters/config.m4 +++ b/modules/filters/config.m4 @@ -11,6 +11,7 @@ APACHE_MODULE(ext_filter, external filter module, , , most) APACHE_MODULE(request, Request Body Filtering, , , yes) APACHE_MODULE(include, Server Side Includes, , , yes) APACHE_MODULE(filter, Smart Filtering, , , yes) +APACHE_MODULE(reflector, Reflect request through the output filter stack, , , yes) APACHE_MODULE(substitute, response content rewrite-like filtering, , , most) sed_obj="mod_sed.lo sed0.lo sed1.lo regexp.lo" diff --git a/modules/filters/mod_reflector.c b/modules/filters/mod_reflector.c new file mode 100644 index 0000000000..e9e02abd07 --- /dev/null +++ b/modules/filters/mod_reflector.c @@ -0,0 +1,231 @@ +/* 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 "apr_tables.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_request.h" + +module AP_MODULE_DECLARE_DATA reflector_module; + +typedef struct { + apr_table_t *headers; +} reflector_cfg; + +static int header_do(void *dummy, const char *key, const char *value) +{ + request_rec *r = (request_rec *) dummy; + const char *payload; + + payload = apr_table_get(r->headers_in, key); + if (payload) { + apr_table_setn(r->headers_out, value, payload); + } + + return 1; +} + +static int reflector_handler(request_rec * r) +{ + apr_bucket_brigade *bbin, *bbout; + apr_bucket *e; + reflector_cfg *conf; + apr_status_t status; + + if (strcmp(r->handler, "reflector")) { + return DECLINED; + } + + conf = (reflector_cfg *) ap_get_module_config(r->per_dir_config, + &reflector_module); + + ap_allow_methods(r, 1, "POST", "OPTIONS", NULL); + + if (r->method_number == M_OPTIONS) { + return ap_send_http_options(r); + } + + else if (r->method_number == M_POST) { + const char *content_length, *content_type; + int seen_eos; + + /* + * Sometimes we'll get in a state where the input handling has + * detected an error where we want to drop the connection, so if + * that's the case, don't read the data as that is what we're trying + * to avoid. + * + * This function is also a no-op on a subrequest. + */ + if (r->main || r->connection->keepalive == AP_CONN_CLOSE || + ap_status_drops_connection(r->status)) { + return OK; + } + + /* copy headers from in to out if configured */ + apr_table_do(header_do, r, conf->headers, NULL); + + /* last modified defaults to now, unless otherwise set on the way in */ + if (!apr_table_get(r->headers_out, "Last-Modified")) { + ap_update_mtime(r, apr_time_now()); + ap_set_last_modified(r); + } + apr_table_setn(r->headers_out, "Accept-Ranges", "bytes"); + + /* reflect the content length, if present */ + if ((content_length = apr_table_get(r->headers_in, "Content-Length"))) { + apr_off_t offset; + + apr_strtoff(&offset, content_length, NULL, 10); + ap_set_content_length(r, offset); + + } + + /* reflect the content type, if present */ + if ((content_type = apr_table_get(r->headers_in, "Content-Type"))) { + + ap_set_content_type(r, content_type); + + } + + bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc); + bbout = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + seen_eos = 0; + do { + apr_bucket *bucket; + + status = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES, + APR_BLOCK_READ, HUGE_STRING_LEN); + + if (status != APR_SUCCESS) { + if (status == AP_FILTER_ERROR) { + apr_brigade_destroy(bbin); + return status; + } + else { + apr_brigade_destroy(bbin); + return HTTP_BAD_REQUEST; + } + } + + for (bucket = APR_BRIGADE_FIRST(bbin); + bucket != APR_BRIGADE_SENTINEL(bbin); + bucket = APR_BUCKET_NEXT(bucket)) { + const char *data; + apr_size_t len; + + if (APR_BUCKET_IS_EOS(bucket)) { + seen_eos = 1; + break; + } + + /* These are metadata buckets. */ + if (bucket->length == 0) { + continue; + } + + /* + * We MUST read because in case we have an unknown-length + * bucket or one that morphs, we want to exhaust it. + */ + status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (status != APR_SUCCESS) { + apr_brigade_destroy(bbin); + return HTTP_BAD_REQUEST; + } + + apr_brigade_write(bbout, NULL, NULL, data, len); + + status = ap_pass_brigade(r->output_filters, bbout); + if (status != APR_SUCCESS) { + /* no way to know what type of error occurred */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, + "default_handler: ap_pass_brigade returned %i", + status); + return HTTP_INTERNAL_SERVER_ERROR; + } + + } + + apr_brigade_cleanup(bbin); + + } while (!seen_eos); + + return OK; + + } + + else { + return HTTP_METHOD_NOT_ALLOWED; + } + +} + +static void *create_reflector_dir_config(apr_pool_t * p, char *d) +{ + reflector_cfg *conf = apr_pcalloc(p, sizeof(reflector_cfg)); + + conf->headers = apr_table_make(p, 8); + + return conf; +} + +static void *merge_reflector_dir_config(apr_pool_t * p, void *basev, void *addv) +{ + reflector_cfg *new = (reflector_cfg *) apr_pcalloc(p, + sizeof(reflector_cfg)); + reflector_cfg *add = (reflector_cfg *) addv; + reflector_cfg *base = (reflector_cfg *) basev; + + new->headers = apr_table_overlay(p, add->headers, base->headers); + + return new; +} + +static const char *reflector_header(cmd_parms * cmd, void *dummy, const char *in, + const char *out) +{ + reflector_cfg *cfg = (reflector_cfg *) dummy; + + apr_table_addn(cfg->headers, in, out ? out : in); + + return NULL; +} + +static void reflector_hooks(apr_pool_t * p) +{ + ap_hook_handler(reflector_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const command_rec reflector_cmds[] = { + AP_INIT_TAKE12("ReflectorHeader", reflector_header, NULL, OR_OPTIONS, + "Header to reflect back in the response, with an optional new name."), + {NULL} +}; + +module AP_MODULE_DECLARE_DATA reflector_module = { + STANDARD20_MODULE_STUFF, + create_reflector_dir_config, + merge_reflector_dir_config, + NULL, + NULL, + reflector_cmds, + reflector_hooks +}; -- 2.50.1