RubyInputFilter/RubyOutputFilter patch

why the lucky stiff <[email protected]> Tue, 27 Dec 2005 08:58:16 -0700
Newsgroups gmane.comp.apache.mod-ruby
Message-ID <[email protected]>
Hi, Shugo and friends.

I'm becoming quite jealous of mod_perl and mod_python folks and their 
Apache2 filter hooks.  So I looked through the mod_python source and 
scratched together some filter code for mod_ruby.  See the patch clipped 
on to this message.

In httpd.conf:

  RubyRequire filter_test
  RubyOutputFilter FilterTest.instance TEST
  SetOutputFilter TEST

In lib/filter_test.rb

  require 'singleton'

  class FilterTest
     include Singleton

     def output_filter(filter)
        s = filter.read
        while s
           filter.write s.gsub(/mod_python/i, 'mod_ruby')
           s = filter.read
        end

        filter.close if filter.eos?
     end
   end

Obviously, this is just a start.  It's probably buggy.  I've ripped off 
ruby_handler pretty disgustingly.  The API could be more Ruby-like.  
Overall, what do you think?

_why
mod_ruby.filters.patch (text/x-patch, 31.8 KB)
Index: Makefile.in
===================================================================
--- Makefile.in	(revision 102)
+++ Makefile.in	(working copy)
@@ -99,7 +99,8 @@
 		  bucket.@OBJEXT@ \
 		  apache_request.@OBJEXT@ \
 		  apache_multipart_buffer.@OBJEXT@ \
-		  apache_cookie.@OBJEXT@
+		  apache_cookie.@OBJEXT@ \
+		  apache_filter.@OBJEXT@
 
 @COMPILE_RULES@
 
@@ -186,3 +187,4 @@
 apache_request.@OBJEXT@: apache_request.c mod_ruby.h apache_request.h
 apache_multipart_buffer.@OBJEXT@: apache_multipart_buffer.c mod_ruby.h apache_request.h apache_multipart_buffer.h
 apache_cookie.@OBJEXT@: apache_cookie.c apache_cookie.h apache_request.h
+apache_filter.@OBJEXT@: apache_filter.c apache_filter.h mod_ruby.h
Index: apachelib.c
===================================================================
--- apachelib.c	(revision 102)
+++ apachelib.c	(working copy)
@@ -424,6 +424,7 @@
     rb_init_apache_cookie();
 #ifdef APACHE2
     rb_init_apache_bucket();
+    rb_init_apache_filter();
 #endif
 }
 
Index: mod_ruby.c
===================================================================
--- mod_ruby.c	(revision 102)
+++ mod_ruby.c	(working copy)
@@ -202,6 +202,12 @@
     {"RubyChildInitHandler", ruby_cmd_child_init_handler,
      NULL, RSRC_CONF, TAKE1,
      "set child init handler object"},
+    {"RubyInputFilter", ruby_cmd_input_filter, 
+     NULL, RSRC_CONF|ACCESS_CONF, TAKE12,
+     "set input filter."},
+    {"RubyOutputFilter", ruby_cmd_output_filter, 
+     NULL, RSRC_CONF|ACCESS_CONF, TAKE12,
+     "set output filter."},
     {NULL}
 };
 
@@ -841,6 +847,7 @@
 }
 
 static int ruby_handler(request_rec *, array_header *, ID, int, int);
+/* static int ruby_filterhdlr(ApacheFilter *,	array_header *, ID, int, int); */
 
 #ifdef APACHE2
 static void ruby_child_init(pool *p, server_rec *s)
@@ -1421,7 +1428,244 @@
 			rb_intern("post_read_request"), 1, 0);
 }
 
+typedef struct filterhdlr_0_arg {
+    ApacheFilter *f;
+    char *filterhdlr;
+    ID mid;
+} filterhdlr_0_arg_t;
+
+static VALUE ruby_filterhdlr_0(void *arg)
+{
+    filterhdlr_0_arg_t *ha = (filterhdlr_0_arg_t *) arg;
+    ApacheFilter *f = ha->f;
+    char *filterhdlr = ha->filterhdlr;
+    ID mid = ha->mid;
+    VALUE ret;
+    int state;
+
+    ret = rb_protect_funcall(rb_eval_string(filterhdlr), mid, &state,
+			     1, f->filter_object);
+    if (state) {
+	if (state == TAG_RAISE &&
+	    rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit)) {
+	    ret = rb_iv_get(ruby_errinfo, "status");
+	}
+	else {
+	    handle_error(f->req, state);
+	    return INT2NUM(HTTP_INTERNAL_SERVER_ERROR);
+	}
+    }
+	return ret;
+}
+
+typedef struct filterhdlr_internal_arg {
+    ApacheFilter *f;
+    array_header *filterhdlrs_arr;
+    ID mid;
+    int run_all;
+    int flush;
+    VALUE retval;
+} filterhdlr_internal_arg_t;
+
+static void *ruby_filterhdlr_internal(filterhdlr_internal_arg_t *iarg)
+{
+    ApacheFilter *f = iarg->f;
+    array_header *filterhdlrs_arr = iarg->filterhdlrs_arr;
+    ID mid = iarg->mid;
+    int run_all = iarg->run_all;
+    int flush = iarg->flush;
+    ruby_server_config *sconf;
+    ruby_dir_config *dconf;
+    int safe_level;
+    int state;
+    VALUE ret;
+    filterhdlr_0_arg_t arg;
+    int i, filterhdlrs_len;
+    char **filterhdlrs;
+
+    sconf = get_server_config(f->req->server);
+    dconf = get_dir_config(f->req);
+    safe_level = dconf ? dconf->safe_level : MR_DEFAULT_SAFE_LEVEL;
+    filterhdlrs = (char **) filterhdlrs_arr->elts;
+    filterhdlrs_len = filterhdlrs_arr->nelts;
+    iarg->retval = DECLINED;
+
+    per_request_init(f->req);
+    for (i = 0; i < filterhdlrs_len; i++) {
+	arg.f = f;
+	arg.filterhdlr = filterhdlrs[i];
+	arg.mid = mid;
+	ap_soft_timeout("call ruby filterhdlr", r);
+	if ((state = run_safely(safe_level, sconf->timeout,
+				ruby_filterhdlr_0, &arg, &ret)) == 0) {
+	    iarg->retval = OK;
+	}
+	else {
+	    handle_error(f->req, state);
+	    iarg->retval = HTTP_INTERNAL_SERVER_ERROR;
+	}
+	ap_kill_timeout(f->req);
+	if (iarg->retval != DECLINED && (!run_all || iarg->retval != OK))
+	    break;
+    }
+    per_request_cleanup(f->req, flush && iarg->retval == OK);
+    return NULL;
+}
+
 /*
+static int ruby_filterhdlr(ApacheFilter *f,
+			array_header *filterhdlrs_arr, ID mid,
+			int run_all, int flush)
+{
+    filterhdlr_internal_arg_t *arg;
+
+    if (filterhdlrs_arr == NULL)
+	return DECLINED;
+
+    arg = ap_palloc(f->req->pool, sizeof(filterhdlr_internal_arg_t));
+    arg->f = f;
+    arg->filterhdlrs_arr = filterhdlrs_arr;
+    arg->mid = mid;
+    arg->run_all = run_all;
+    arg->flush = flush;
+    arg->retval = 0;
+#if APR_HAS_THREADS
+    if (ruby_is_threaded_mpm) {
+	apr_status_t status;
+	char buf[256];
+
+	status =
+	    ruby_call_interpreter(f->req->pool,
+				  (ruby_interp_func_t) ruby_filterhdlr_internal,
+				  arg, NULL, 0);
+	if (status != APR_SUCCESS) {
+	    apr_strerror(status, buf, sizeof(buf));
+	    ruby_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, f->req->server,
+			   "ruby_call_interpreter() failed: %s", buf);
+	    return HTTP_INTERNAL_SERVER_ERROR;
+	}
+    }
+    else {
+#endif
+	ruby_filterhdlr_internal(arg);
+#if APR_HAS_THREADS
+    }
+#endif
+    return arg->retval;
+}
+*/
+
+static apr_status_t ruby_filter(int is_input, ap_filter_t *f, 
+                                  apr_bucket_brigade *bb,
+                                  ap_input_mode_t mode,
+                                  apr_read_type_e block,
+                                  apr_size_t readbytes) {
+    filterhdlr_internal_arg_t *arg;
+
+    ruby_dir_config * conf;
+    request_rec *r;
+    ApacheFilter *filter;
+    VALUE filter_obj;
+    ruby_filter_ctx *ctx;
+    ruby_filter_handler *fh;
+    array_header *hdlr = NULL;
+    ID mid;
+
+    /* we only allow request level filters so far */
+    r = f->r;
+
+    /* create ctx if not there yet */
+    if (!f->ctx) {
+        ctx = (ruby_filter_ctx *) apr_pcalloc(r->pool, sizeof(ruby_filter_ctx));
+        f->ctx = (void *)ctx;
+    }
+    else {
+        ctx = (ruby_filter_ctx *) f->ctx;
+    }
+        
+    /* are we in transparent mode? transparent mode is on after an error,
+       so a filter can spit out an error without causing infinite loop */
+    if (ctx->transparent) {
+        if (is_input) 
+            return ap_get_brigade(f->next, bb, mode, block, readbytes);
+        else
+            return ap_pass_brigade(f->next, bb);
+    }
+        
+    /* get configuration */
+    conf = get_dir_config(r);
+
+    /* the name of ruby function to call */
+    if (is_input)
+    {
+        fh = apr_hash_get(conf->in_filters, f->frec->name, APR_HASH_KEY_STRING);
+        mid = rb_intern("input_filter");
+    }
+    else
+    {
+        fh = apr_hash_get(conf->out_filters, f->frec->name, APR_HASH_KEY_STRING);
+        mid = rb_intern("output_filter");
+    }
+
+    /* create filter */
+    filter_obj = apache_filter_new(f, bb, is_input, mode, readbytes,
+                                                 fh->handler, fh->dir);
+    filter = get_filter_data(filter_obj);
+    filter->req = r;
+
+    hdlr = ap_make_array(r->pool, 1, sizeof(char*));
+    *(char **) ap_push_array(hdlr) = fh->handler;
+
+    arg = ap_palloc(r->pool, sizeof(filterhdlr_internal_arg_t));
+    arg->f = filter;
+    arg->filterhdlrs_arr = hdlr;
+    arg->mid = mid;
+    arg->run_all = 0;
+    arg->flush = 0;
+    arg->retval = 0;
+#if APR_HAS_THREADS
+    if (ruby_is_threaded_mpm) {
+	apr_status_t status;
+	char buf[256];
+
+	status =
+	    ruby_call_interpreter(r->pool,
+				  (ruby_interp_func_t) ruby_filterhdlr_internal,
+				  arg, NULL, 0);
+	if (status != APR_SUCCESS) {
+	    apr_strerror(status, buf, sizeof(buf));
+	    ruby_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server,
+			   "ruby_call_interpreter() failed: %s", buf);
+	    return HTTP_INTERNAL_SERVER_ERROR;
+	}
+    }
+    else {
+#endif
+	ruby_filterhdlr_internal(arg);
+#if APR_HAS_THREADS
+    }
+#endif
+    
+    rb_funcall(filter_obj, rb_intern("flush"), 0);
+    return filter->rc;
+}
+
+apr_status_t ruby_input_filter(ap_filter_t *f, 
+                                        apr_bucket_brigade *bb,
+                                        ap_input_mode_t mode,
+                                        apr_read_type_e block,
+                                        apr_off_t readbytes)
+{
+    return ruby_filter(1, f, bb, mode, block, readbytes);
+}
+
+apr_status_t ruby_output_filter(ap_filter_t *f, 
+                                         apr_bucket_brigade *bb)
+{
+    return ruby_filter(0, f, bb, 0, 0, 0);
+}
+
+/*
  * Local variables:
  * mode: C
  * tab-width: 8
Index: mod_ruby.h
===================================================================
--- mod_ruby.h	(revision 102)
+++ mod_ruby.h	(working copy)
@@ -134,6 +134,7 @@
 #include "apache_request.h"
 #include "apache_multipart_buffer.h"
 #include "apache_cookie.h"
+#include "apache_filter.h"
 
 #define MOD_RUBY_STRING_VERSION "mod_ruby/1.2.4"
 #define RUBY_GATEWAY_INTERFACE "CGI-Ruby/1.1"
@@ -153,6 +154,7 @@
     int output_mode;
     array_header *load_path;
     table *options;
+    char *config_dir;
     array_header *ruby_handler;
     array_header *ruby_trans_handler;
     array_header *ruby_authen_handler;
@@ -165,6 +167,8 @@
     array_header *ruby_post_read_request_handler;
     array_header *ruby_init_handler;
     array_header *ruby_cleanup_handler;
+    apr_hash_t   *in_filters;
+    apr_hash_t   *out_filters;
 } ruby_dir_config;
 
 typedef struct {
@@ -178,6 +182,20 @@
     VALUE request_object;
 } ruby_request_config;
 
+/* filter context */
+typedef struct
+{   
+    int transparent;
+} ruby_filter_ctx;
+
+/* a structure to hold a handler, 
+ * used in configuration for filters */
+typedef struct
+{   
+    char *handler;
+    char *dir;
+} ruby_filter_handler;
+
 #define MR_DEFAULT_TIMEOUT 270
 #define MR_DEFAULT_SAFE_LEVEL 1
 #define MR_DEFAULT_RESTRICT_DIRECTIVES 0
@@ -230,6 +248,13 @@
 
 apr_status_t ruby_call_interpreter(pool *p, ruby_interp_func_t func,
 				   void *arg, void **result, int *state);
+apr_status_t ruby_input_filter(ap_filter_t *f, 
+                   apr_bucket_brigade *bb,
+                   ap_input_mode_t mode,
+                   apr_read_type_e block,
+                   apr_off_t readbytes);
+apr_status_t ruby_output_filter(ap_filter_t *f, 
+                   apr_bucket_brigade *bb);
 #endif
 
 #endif /* !MOD_RUBY_H */
Index: ruby_config.c
===================================================================
--- ruby_config.c	(revision 102)
+++ ruby_config.c	(working copy)
@@ -89,7 +89,7 @@
     return (void *) new;
 }
 
-void *ruby_create_dir_config(pool *p, char *dirname)
+void *ruby_create_dir_config(pool *p, char *dir)
 {
     ruby_dir_config *conf =
 	(ruby_dir_config *) ap_palloc(p, sizeof (ruby_dir_config));
@@ -112,6 +112,15 @@
     conf->ruby_post_read_request_handler = NULL;
     conf->ruby_init_handler = NULL;
     conf->ruby_cleanup_handler = NULL;
+    conf->in_filters = apr_hash_make(p);
+    conf->out_filters = apr_hash_make(p);
+
+    /* make sure directory ends with a slash */
+    if (dir && (dir[strlen(dir) - 1] != '/'))
+        conf->config_dir = apr_pstrcat(p, dir, "/", NULL);
+    else
+        conf->config_dir = apr_pstrdup(p, dir);
+
     return conf;
 }
 
@@ -122,6 +131,11 @@
     ruby_dir_config *base = (ruby_dir_config *) basev;
     ruby_dir_config *add = (ruby_dir_config *) addv;
 
+    apr_hash_index_t *hi;
+    char *key;
+    apr_ssize_t klen;
+    void *hle;
+
     new->kcode = add->kcode ? add->kcode : base->kcode;
     new->env = ap_overlay_tables(p, add->env, base->env);
     if (add->safe_level >= base->safe_level) {
@@ -143,8 +157,36 @@
 	new->load_path = ap_append_arrays(p, base->load_path, add->load_path);
     }
 
+    /* copy base */
     new->options = ap_overlay_tables(p, add->options, base->options);
+    new->in_filters = apr_hash_make(p);
+    new->out_filters = apr_hash_make(p);
+    new->config_dir = apr_pstrdup(p, base->config_dir);
 
+    for (hi = apr_hash_first(p, base->in_filters); hi; hi=apr_hash_next(hi)) {
+        apr_hash_this(hi, (const void **)&key, &klen, &hle);
+        apr_hash_set(new->in_filters, key, klen, hle);
+    }
+
+    for (hi = apr_hash_first(p, base->out_filters); hi; hi=apr_hash_next(hi)) {
+        apr_hash_this(hi, (const void **)&key, &klen, &hle);
+        apr_hash_set(new->out_filters, key, klen, hle);
+    }
+
+    /* copy new */
+    if (add->config_dir)
+        new->config_dir = apr_pstrdup(p, add->config_dir);
+
+    for (hi = apr_hash_first(p, add->in_filters); hi; hi=apr_hash_next(hi)) {
+        apr_hash_this(hi, (const void**)&key, &klen, &hle);
+        apr_hash_set(new->in_filters, key, klen, hle);
+    }
+
+    for (hi = apr_hash_first(p, add->out_filters); hi; hi=apr_hash_next(hi)) {
+        apr_hash_this(hi, (const void**)&key, &klen, &hle);
+        apr_hash_set(new->out_filters, key, klen, hle);
+    }
+
     new->ruby_handler =
 	merge_handlers(p, base->ruby_handler, add->ruby_handler);
     new->ruby_trans_handler =
@@ -503,6 +545,57 @@
     return NULL;
 }
 
+
+const char *ruby_cmd_input_filter(cmd_parms *cmd, ruby_dir_config *conf, 
+					const char *handler, const char *name)
+{
+    ruby_filter_handler *fh;
+    ap_filter_rec_t *frec;
+
+    check_restrict_directives(cmd, conf)
+
+    if (!name)
+        name = apr_pstrdup(cmd->pool, handler);
+
+    /* register the filter NOTE - this only works so long as the
+       directive is only allowed in the main config. For .htaccess we
+       would have to make sure not to duplicate this */
+    frec = ap_register_input_filter(name, ruby_input_filter, NULL, AP_FTYPE_RESOURCE);
+ 
+    fh = (ruby_filter_handler *) apr_pcalloc(cmd->pool, sizeof(ruby_filter_handler));
+    fh->handler = (char *)handler;
+    fh->dir = conf->config_dir;
+
+    apr_hash_set(conf->in_filters, frec->name, APR_HASH_KEY_STRING, fh);
+
+    return NULL;
+}
+
+const char *ruby_cmd_output_filter(cmd_parms *cmd, ruby_dir_config *conf, 
+					const char *handler, const char *name)
+{
+    ruby_filter_handler *fh;
+    ap_filter_rec_t *frec;
+
+    check_restrict_directives(cmd, conf)
+
+    if (!name)
+        name = apr_pstrdup(cmd->pool, handler);
+
+    /* register the filter NOTE - this only works so long as the
+       directive is only allowed in the main config. For .htaccess we
+       would have to make sure not to duplicate this */
+    frec = ap_register_output_filter(name, ruby_output_filter, NULL, AP_FTYPE_RESOURCE);
+ 
+    fh = (ruby_filter_handler *) apr_pcalloc(cmd->pool, sizeof(ruby_filter_handler));
+    fh->handler = (char *)handler;
+    fh->dir = conf->config_dir;
+
+    apr_hash_set(conf->out_filters, frec->name, APR_HASH_KEY_STRING, fh);
+
+    return NULL;
+}
+
 /*
  * Local variables:
  * mode: C
Index: ruby_config.h
===================================================================
--- ruby_config.h	(revision 102)
+++ ruby_config.h	(working copy)
@@ -57,6 +57,10 @@
 const char *ruby_cmd_init_handler(cmd_parms*, ruby_dir_config*, char*);
 const char *ruby_cmd_cleanup_handler(cmd_parms*, ruby_dir_config*, char*);
 const char *ruby_cmd_child_init_handler(cmd_parms*, void*, char*);
+const char *ruby_cmd_input_filter(cmd_parms*, ruby_dir_config*, 
+					const char*, const char *);
+const char *ruby_cmd_output_filter(cmd_parms*, ruby_dir_config*, 
+					const char*, const char*);
 
 #endif /* !RUBY_CONFIG_H */
 
Index: apache_filter.c
===================================================================
--- apache_filter.c	(revision 0)
+++ apache_filter.c	(revision 0)
@@ -0,0 +1,456 @@
+/*
+ * $Id: filter.c 93 2005-08-03 01:32:41Z why $
+ * Copyright (C) 2001  Shugo Maeda <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "mod_ruby.h"
+#include "apachelib.h"
+
+VALUE rb_cApacheFilter;
+
+static APR_CLEANUP_RETURN_TYPE cleanup_filter_object(void *data)
+{
+    VALUE obj;
+    ApacheFilter *f = (ApacheFilter *) data;
+
+    if (f->filter_object == Qnil)
+        APR_CLEANUP_RETURN_SUCCESS();
+
+    obj = f->filter_object;
+    if (TYPE(obj) == T_DATA) {
+        free(RDATA(obj)->data);
+        RDATA(obj)->data = NULL;
+    }
+#if APR_HAS_THREADS
+    if (ruby_is_threaded_mpm) {
+	ruby_call_interpreter(f->req->pool,
+			      (ruby_interp_func_t) rb_apache_unregister_object,
+			      (void *) obj, NULL, 0);
+    }
+    else {
+#endif
+	rb_apache_unregister_object(obj);
+#if APR_HAS_THREADS
+    }
+#endif
+    APR_CLEANUP_RETURN_SUCCESS();
+}
+
+VALUE apache_filter_new(ap_filter_t *f, apr_bucket_brigade *bb, int is_input,
+                       ap_input_mode_t mode, apr_size_t readbytes,
+                       char * handler, char *dir)
+{
+    ApacheFilter *filter;
+    VALUE obj = Data_Make_Struct(rb_cApacheFilter, ApacheFilter, NULL, free, filter);
+    filter->f = f;
+    filter->is_input = is_input;
+
+    filter->rc = APR_SUCCESS;
+
+    if (is_input) {
+        filter->bb_in = NULL;
+        filter->bb_out = bb;
+        filter->mode = mode;
+        filter->readbytes = readbytes;
+    }
+    else {
+        filter->bb_in = bb;
+        filter->bb_out = NULL;
+        filter->mode = 0;
+        filter->readbytes = 0;
+    }
+
+    filter->eos = 0;
+    filter->closed = 0;
+    filter->softspace = 0;
+
+    filter->handler = handler;
+    filter->dir = dir;
+
+    filter->req = NULL; 
+
+    rb_apache_register_object(obj);
+    filter->filter_object = obj;
+
+    apr_pool_cleanup_register(f->r->pool, (void *)filter, cleanup_filter_object, 
+                              apr_pool_cleanup_null);
+
+    return obj;
+}
+
+ApacheFilter *get_filter_data(VALUE obj)
+{
+    ApacheFilter *filter;
+
+    Check_Type(obj, T_DATA);
+    filter = (ApacheFilter *) RDATA(obj)->data;
+    if (filter == NULL)
+        rb_raise(rb_eArgError, "destroyed request");
+    return filter;
+}
+
+static VALUE rb_filter_pass_on(VALUE self)
+{
+    ApacheFilter *filter;
+
+    filter = get_filter_data(self);
+    if (filter->is_input) 
+        filter->rc = ap_get_brigade(filter->f->next, filter->bb_out, 
+                                    filter->mode, APR_BLOCK_READ, 
+                                    filter->readbytes);
+    else
+        filter->rc = ap_pass_brigade(filter->f->next, filter->bb_in);
+
+    return Qnil;
+}
+
+static VALUE _rb_filter_read(int argc, VALUE *argv, VALUE self, int readline)
+{
+    apr_bucket *b;
+    long bytes_read;
+    VALUE result;
+    int newline = 0;
+    long len = -1;
+    VALUE lenv;
+    ApacheFilter *filter = get_filter_data(self);
+    conn_rec *c = filter->req->connection;
+
+    if (rb_scan_args(argc, argv, "01", &lenv) == 1)
+    {
+        len = NUM2INT(lenv);
+    }
+
+    if (filter->closed) {
+        rb_raise(rb_eTypeError, "I/O operation on closed filter");
+        return Qnil;
+    }
+
+    if (filter->is_input) {
+
+        /* does the output brigade exist? */
+        if (!filter->bb_in) {
+            filter->bb_in = apr_brigade_create(filter->f->r->pool, 
+                                             c->bucket_alloc);
+        }
+
+        filter->rc = ap_get_brigade(filter->f->next, filter->bb_in, filter->mode, 
+                                  APR_BLOCK_READ, filter->readbytes);
+
+        if (!APR_STATUS_IS_EAGAIN(filter->rc) && !APR_STATUS_IS_SUCCESS(filter->rc)) {
+            rb_raise(rb_eIOError, "Input filter read error");
+            return Qnil;
+        }
+    }
+
+    /* 
+     * loop through the brigade reading buckets into the string 
+     */
+
+    b = APR_BRIGADE_FIRST(filter->bb_in);
+
+    if (b == APR_BRIGADE_SENTINEL(filter->bb_in))
+        return Qnil;
+    // return rb_str_new2("");
+
+    /* reached eos ? */
+    if (APR_BUCKET_IS_EOS(b)) {
+        apr_bucket_delete(b);
+        filter->eos = 1;
+        return Qnil;
+    }
+
+    result = rb_str_new2("");
+
+    bytes_read = 0;
+
+    while ((bytes_read < len || len == -1) && 
+           !(APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b) ||
+             b == APR_BRIGADE_SENTINEL(filter->bb_in))) {
+
+        const char *data;
+        apr_size_t size;
+        apr_bucket *old;
+        int i;
+
+        if (apr_bucket_read(b, &data, &size, APR_BLOCK_READ) != APR_SUCCESS) {
+            rb_raise(rb_eIOError, "Filter read error");
+            return Qnil;
+        }
+
+        if (readline) {
+
+            /* scan for newline */
+            for (i=0; i<size; i++) {
+                if (data[i] == '\n') {
+                    if (i+1 != size) {   /* (no need to split if we're at end of bucket) */
+                        
+                        /* split after newline */
+                        apr_bucket_split(b, i+1);   
+                        size = i + 1;
+                    }
+                    newline = 1;
+                    break;
+                }
+            }
+        }
+
+        rb_str_cat(result, data, (long)size);
+        bytes_read += size;
+
+        if (readline && newline) {
+            apr_bucket_delete(b);
+            break;
+        }
+
+        old = b;
+        b = APR_BUCKET_NEXT(b);
+        apr_bucket_delete(old);
+        
+/*         if (filter->is_input) { */
+
+/*             if (b == APR_BRIGADE_SENTINEL(filter->bb_in)) { */
+/*                 /\* brigade ended, but no EOS - get another */
+/*                    brigade *\/ */
+
+/*                 Py_BEGIN_ALLOW_THREADS; */
+/*                 filter->rc = ap_get_brigade(filter->f->next, filter->bb_in, filter->mode,  */
+/*                                           APR_BLOCK_READ, filter->readbytes); */
+/*                 Py_END_ALLOW_THREADS; */
+
+/*                 if (! APR_STATUS_IS_SUCCESS(filter->rc)) { */
+/*                     PyErr_SetObject(PyExc_IOError,  */
+/*                                     PyString_FromString("Input filter read error")); */
+/*                     return NULL; */
+/*                 } */
+/*                 b = APR_BRIGADE_FIRST(filter->bb_in); */
+/*             } */
+/*         }  */
+    }
+
+    return result;
+}
+
+static VALUE rb_filter_read(int argc, VALUE *argv, VALUE self)
+{
+    return _rb_filter_read(argc, argv, self, 0);
+}
+
+static VALUE rb_filter_readline(int argc, VALUE *argv, VALUE self)
+{
+    return _rb_filter_read(argc, argv, self, 1);
+}
+
+static VALUE rb_filter_write(VALUE self, VALUE s)
+{
+    char *buff;
+    int len;
+    apr_bucket *b;
+    ApacheFilter *filter = get_filter_data(self);
+    conn_rec *c = filter->req->connection;
+    VALUE tmp, port;
+    int taint;
+
+    if (NIL_P(tmp = rb_check_string_type(s))) {
+        rb_raise(rb_eTypeError, "Argument to write() must be a string");
+        return Qnil;
+    }
+    taint = OBJ_TAINTED(port); /* original taintedness */
+    port = tmp;
+
+    if (filter->closed) {
+        rb_raise(rb_eTypeError, "I/O operation on closed filter");
+        return Qnil;
+    }
+
+    len = RSTRING(s)->len;
+
+    if (len) {
+
+        /* does the output brigade exist? */
+        if (!filter->bb_out) {
+            filter->bb_out = apr_brigade_create(filter->f->r->pool, 
+                                              c->bucket_alloc);
+        }
+        
+        buff = apr_bucket_alloc(len, c->bucket_alloc);
+        memcpy(buff, RSTRING(s)->ptr, len);
+
+        b = apr_bucket_heap_create(buff, len, apr_bucket_free,
+                                   c->bucket_alloc);
+
+        APR_BRIGADE_INSERT_TAIL(filter->bb_out, b);
+    }
+
+    return Qnil;
+}
+
+static VALUE rb_filter_flush(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    conn_rec *c = filter->req->connection;
+
+    /* does the output brigade exist? */
+    if (!filter->bb_out) {
+        filter->bb_out = apr_brigade_create(filter->f->r->pool,
+                                          c->bucket_alloc);
+    }
+
+    APR_BRIGADE_INSERT_TAIL(filter->bb_out, 
+                            apr_bucket_flush_create(c->bucket_alloc));
+
+    if (!filter->is_input) {
+
+        filter->rc = ap_pass_brigade(filter->f->next, filter->bb_out);
+        apr_brigade_destroy(filter->bb_out);
+
+        if(filter->rc != APR_SUCCESS) { 
+            rb_raise(rb_eIOError, "Flush failed.");
+            return Qnil;
+        }
+    }
+
+    return Qnil;
+}
+
+static VALUE rb_filter_close(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    conn_rec *c = filter->req->connection;
+
+    if (! filter->closed) {
+
+        /* does the output brigade exist? */
+        if (!filter->bb_out) {
+            filter->bb_out = apr_brigade_create(filter->f->r->pool,
+                                              c->bucket_alloc);
+        }
+
+        APR_BRIGADE_INSERT_TAIL(filter->bb_out, 
+                                apr_bucket_eos_create(c->bucket_alloc));
+
+        if (! filter->is_input) {
+            filter->rc = ap_pass_brigade(filter->f->next, filter->bb_out);
+            apr_brigade_destroy(filter->bb_out);
+            filter->bb_out = NULL;
+        }
+
+        filter->closed = 1;
+    }
+
+    return Qnil;
+}
+
+static VALUE rb_filter_disable(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    ruby_filter_ctx *ctx;
+
+    ctx = (ruby_filter_ctx *) filter->f->ctx;
+    ctx->transparent = 1;
+
+    return Qnil;
+
+}
+
+static VALUE rb_filter_attr_name(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    if (! filter->f->frec->name) {
+        return Qnil;
+    } else {
+        return rb_str_new2(filter->f->frec->name);
+    }
+}
+
+static VALUE rb_filter_attr_req(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return rb_get_request_object(filter->req);
+}
+
+static VALUE rb_filter_attr_softspace(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return INT2FIX(filter->softspace);
+}
+
+static VALUE rb_filter_attr_closed(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return filter->closed >= 1 ? Qtrue : Qfalse;
+}
+
+static VALUE rb_filter_attr_eos(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return filter->eos >= 1 ? Qtrue : Qfalse;
+}
+
+static VALUE rb_filter_attr_is_input(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return filter->is_input >= 1 ? Qtrue : Qfalse;
+}
+
+static VALUE rb_filter_attr_handler(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return rb_str_new2(filter->handler);
+}
+
+static VALUE rb_filter_attr_dir(VALUE self)
+{
+    ApacheFilter *filter = get_filter_data(self);
+    return rb_str_new2(filter->dir);
+}
+
+/* Module initializer */
+void rb_init_apache_filter()
+{
+    /* Kluge to make Rdoc see the associations in this file */
+#if FOR_RDOC_PARSER
+    rb_mApache = rb_define_module( "Apache" );
+#endif
+
+    rb_cApacheFilter = rb_define_class_under( rb_mApache, "Filter", rb_cObject );
+    rb_undef_method(CLASS_OF(rb_cApacheFilter), "new");
+    rb_define_method(rb_cApacheFilter, "pass_on", rb_filter_pass_on, 0);
+    rb_define_method(rb_cApacheFilter, "read", rb_filter_read, -1);
+    rb_define_method(rb_cApacheFilter, "readline", rb_filter_readline, -1);
+    rb_define_method(rb_cApacheFilter, "write", rb_filter_write, 1);
+    rb_define_method(rb_cApacheFilter, "flush", rb_filter_flush, 0);
+    rb_define_method(rb_cApacheFilter, "close", rb_filter_close, 0);
+    rb_define_method(rb_cApacheFilter, "disable", rb_filter_disable, 0);
+    rb_define_method(rb_cApacheFilter, "softspace", rb_filter_attr_softspace, 0);
+    rb_define_method(rb_cApacheFilter, "closed?", rb_filter_attr_closed, 0);
+    rb_define_method(rb_cApacheFilter, "eos?", rb_filter_attr_eos, 0);
+    rb_define_method(rb_cApacheFilter, "name", rb_filter_attr_name, 0);
+    rb_define_method(rb_cApacheFilter, "req", rb_filter_attr_req, 0);
+    rb_define_method(rb_cApacheFilter, "is_input", rb_filter_attr_is_input, 0);
+    rb_define_method(rb_cApacheFilter, "handler", rb_filter_attr_handler, 0);
+    rb_define_method(rb_cApacheFilter, "dir", rb_filter_attr_dir, 0);
+}
+
+/* vim: set filetype=c ts=8 sw=4 : */
Index: request.c
===================================================================
--- request.c	(revision 102)
+++ request.c	(working copy)
@@ -45,29 +45,6 @@
 static ID id_post_max, id_disable_uploads,
     id_temp_dir, id_hook_data, id_upload_hook;
 
-typedef struct request_data {
-    request_rec *request;
-    VALUE outbuf;
-    VALUE connection;
-    VALUE server;
-    VALUE headers_in;
-    VALUE headers_out;
-    VALUE err_headers_out;
-    VALUE subprocess_env;
-    VALUE notes;
-    VALUE finfo;
-    VALUE attributes;
-    VALUE error_message;
-    VALUE exception;
-    ApacheRequest *apreq;
-    VALUE upload_hook;
-    VALUE upload_hook_arg;
-    VALUE upload_table;
-    VALUE cookies;
-    VALUE param_table;
-    VALUE options;
-} request_data;
-
 #define REQ_SYNC_HEADER     FL_USER1
 #define REQ_SYNC_OUTPUT     FL_USER2
 #define REQ_HEADER_PENDING  FL_USER3
Index: apache_filter.h
===================================================================
--- apache_filter.h	(revision 0)
+++ apache_filter.h	(revision 0)
@@ -0,0 +1,83 @@
+/* Copyright 1999-2004 The Apache Software Foundation
+
+   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.
+*/
+/* Modified by Shugo Maeda for mod_ruby. */
+
+
+#ifndef _APACHE_FILTER_H
+#define _APACHE_FILTER_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct request_data {
+    request_rec *request;
+    VALUE outbuf;
+    VALUE connection;
+    VALUE server;
+    VALUE headers_in;
+    VALUE headers_out;
+    VALUE err_headers_out;
+    VALUE subprocess_env;
+    VALUE notes;
+    VALUE finfo;
+    VALUE attributes;
+    VALUE error_message;
+    VALUE exception;
+    ApacheRequest *apreq;
+    VALUE upload_hook;
+    VALUE upload_hook_arg;
+    VALUE upload_table;
+    VALUE cookies;
+    VALUE param_table;
+    VALUE options;
+} request_data;
+
+typedef struct {
+    ap_filter_t        *f;
+
+    /* in out refers to the dircetion of data with respect to
+       filter, not the filter type */
+    apr_bucket_brigade *bb_in; 
+    apr_bucket_brigade *bb_out;
+
+    apr_status_t rc;
+
+    int is_input;
+    ap_input_mode_t mode;
+    apr_size_t readbytes;
+
+    int eos;
+    int closed;
+    int softspace;
+    int bytes_written;
+
+    char *handler;
+    char *dir;
+
+    request_rec *req;
+    VALUE filter_object;
+
+} ApacheFilter;
+
+VALUE apache_filter_new(ap_filter_t *f, apr_bucket_brigade *bb, int is_input,
+                        ap_input_mode_t mode, apr_size_t readbytes,
+                        char * handler, char *dir);
+ApacheFilter *get_filter_data(VALUE obj);
+void rb_init_apache_filter();
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !_APACHE_FILTER_H */