<module>mod_ext_filter</module>
<module>mod_include</module>
<module>mod_charset_lite</module>
+ <module>mod_reflector</module>
</modulelist>
<directivelist>
<directive module="mod_filter">FilterChain</directive>
<directive module="mod_mime">AddOutputFilter</directive>
<directive module="mod_mime">RemoveInputFilter</directive>
<directive module="mod_mime">RemoveOutputFilter</directive>
+ <directive module="mod_reflector">ReflectorHeader</directive>
<directive module="mod_ext_filter">ExtFilterDefine</directive>
<directive module="mod_ext_filter">ExtFilterOptions</directive>
<directive module="core">SetInputFilter</directive>
</ul>
</section>
+<section id="service">
+
+<title>Exposing Filters as an HTTP Service</title>
+<p>Filters can be used to process content originating from the client in
+addition to processing content originating on the server using the
+<module>mod_reflector</module> module.</p>
+
+<p><module>mod_reflector</module> 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.</p>
+
+<p>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 <module>mod_deflate</module>
+module might be used to provide a general compression service, or an image
+transformation filter might be turned into an image transformation service.</p>
+
+</section>
+
<section id="using">
<title>Using Filters</title>
<p>There are two ways to use filtering: Simple and Dynamic.
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 894290 $ -->
+
+<!--
+ 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.
+-->
+
+<modulesynopsis metafile="mod_reflector.xml.meta">
+
+<name>mod_reflector</name>
+<description>Reflect a request body as a response via the output filter stack.</description>
+<status>Base</status>
+<sourcefile>mod_reflector.c</sourcefile>
+<identifier>reflector_module</identifier>
+<compatibility>Version 2.3 and later</compatibility>
+
+<summary>
+ <p>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.</p>
+</summary>
+
+<section id="examples"><title>Examples</title>
+ <dl>
+ <dt>Compression service</dt>
+ <dd>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.
+ <example>
+ <Location /compress><br/>
+ SetHandler reflector<br/>
+ SetOutputFilter DEFLATE<br/>
+ </Location>
+ </example>
+ </dd>
+
+ <dt>Image downsampling service</dt>
+ <dd>Pass the request body through an image downsampling filter, and reflect
+ the results to the caller.
+ <example>
+ <Location /downsample><br/>
+ SetHandler reflector<br/>
+ SetOutputFilter DOWNSAMPLE<br/>
+ </Location>
+ </example>
+ </dd>
+ </dl>
+</section>
+
+<directivesynopsis>
+<name>ReflectorHeader</name>
+<description>Reflect an input header to the output headers</description>
+<syntax>ReflectorHeader <var>inputheader</var> <var>[outputheader]</var></syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+<override>Options</override>
+
+<usage>
+ <p>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.</p>
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>
--- /dev/null
+/* 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
+};