You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Brian Akins <br...@turner.com> on 2006/05/23 17:10:00 UTC

[PATCH] setenvif filter

This patch add a filter to mod_setenvif that lets it match against 
response headers as well as request headers.  It is probably a horrible 
implementation, but submitted to encourage others to think of the idea.

This changes the configuration to allow another optional field to 
designate the "mode" of the match (default is request)

SetEnvIf response Content-Type text/* is_text=1

will match against the response header content-type.

The main purpose of this is to allow configurations such as:

AddOutputFilterByType DEFLATE text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
SetEnvIf response Content-Type text/html user-agent-vary=1
Header append Vary User-Agent env=user-agent-vary

With this patch, the correct vary headers are added in a reverse proxy 
situation.

most of the code was adapted from mod_headers.

Thoughts?

-- 
Brian Akins
Lead Systems Engineer
CNN Internet Technologies

Re: [PATCH] setenvif filter

Posted by Brian Akins <ba...@web.turner.com>.
Any thoughts on this?  This has been brought up a few times.

Good usage example, I need to be able to set a header based upon a 
reponse's content type.  I would like to be able to do:


SetEnvIf response Content-Type text/html is_html=1
Header append Cache-Control private env=is_html


But there seems to be no intuitive way with current code...


Brian Akins wrote:
> Here's a newer version with some special handling for content-type.  In 
> response headers, we need to match r->content_type rather than the header.
> 
> 
> 
> ------------------------------------------------------------------------
> 
> --- mod_setenvif.c.bak	2006-05-23 10:08:56.000000000 -0400
> +++ mod_setenvif.c	2006-05-23 16:55:50.000000000 -0400
> @@ -94,6 +94,8 @@
>  #include "http_log.h"
>  #include "http_protocol.h"
>  
> +#define SETENVIF_REQUEST 1
> +#define SETENVIF_RESPONSE 2
>  
>  enum special {
>      SPECIAL_NOT,
> @@ -102,7 +104,8 @@
>      SPECIAL_REQUEST_URI,
>      SPECIAL_REQUEST_METHOD,
>      SPECIAL_REQUEST_PROTOCOL,
> -    SPECIAL_SERVER_ADDR
> +    SPECIAL_SERVER_ADDR,
> +    SPECIAL_CONTENT_TYPE
>  };
>  typedef struct {
>      char *name;                 /* header name */
> @@ -113,12 +116,15 @@
>      apr_table_t *features;      /* env vars to set (or unset) */
>      enum special special_type;  /* is it a "special" header ? */
>      int icase;                  /* ignoring case? */
> +    int mode;                  /*request or response*/
>  } sei_entry;
>  
>  typedef struct {
>      apr_array_header_t *conditionals;
>  } sei_cfg_rec;
>  
> +static ap_filter_rec_t *setenvif_output_filter_handle = NULL;
> +
>  module AP_MODULE_DECLARE_DATA setenvif_module;
>  
>  /*
> @@ -249,7 +255,7 @@
>  }
>  
>  static const char *add_setenvif_core(cmd_parms *cmd, void *mconfig,
> -                                     char *fname, const char *args)
> +                                     char *fname, int mode, const char *args)
>  {
>      char *regex;
>      const char *simple_pattern;
> @@ -304,6 +310,7 @@
>  
>          /* no match, create a new entry */
>          new = apr_array_push(sconf->conditionals);
> +        new->mode = mode;
>          new->name = fname;
>          new->regex = regex;
>          new->icase = icase;
> @@ -345,6 +352,9 @@
>          else if (!strcasecmp(fname, "server_addr")) {
>              new->special_type = SPECIAL_SERVER_ADDR;
>          }
> +        else if ((SETENVIF_RESPONSE == mode) && !strcasecmp(fname, "content-type")) {
> +            new->special_type = SPECIAL_CONTENT_TYPE;
> +        }
>          else {
>              new->special_type = SPECIAL_NOT;
>              /* Handle fname as a regular expression.
> @@ -400,15 +410,35 @@
>  static const char *add_setenvif(cmd_parms *cmd, void *mconfig,
>                                  const char *args)
>  {
> -    char *fname;
> -
> +    char *fname = NULL;
> +    int mode = SETENVIF_REQUEST;
> +    
>      /* get header name */
>      fname = ap_getword_conf(cmd->pool, &args);
> -    if (!*fname) {
> +    /*is this a mode?*/
> +    
> +    if (!fname) {
> +        return apr_pstrcat(cmd->pool, "Missing header-field name for ",
> +                           cmd->cmd->name, NULL);
> +    }
> +    
> +    if(!strcasecmp(fname, "request")) {
> +        mode = SETENVIF_REQUEST;
> +        fname = NULL;
> +    } else if (!strcasecmp(fname, "response")) {
> +        mode = SETENVIF_RESPONSE;
> +        fname = NULL;
> +    } 
> +    
> +    if(!fname) {
> +        fname = ap_getword_conf(cmd->pool, &args);
> +    }
> +    
> +    if (!fname) {
>          return apr_pstrcat(cmd->pool, "Missing header-field name for ",
>                             cmd->cmd->name, NULL);
>      }
> -    return add_setenvif_core(cmd, mconfig, fname, args);
> +    return add_setenvif_core(cmd, mconfig, fname, mode, args);
>  }
>  
>  /*
> @@ -418,7 +448,7 @@
>   */
>  static const char *add_browser(cmd_parms *cmd, void *mconfig, const char *args)
>  {
> -    return add_setenvif_core(cmd, mconfig, "User-Agent", args);
> +    return add_setenvif_core(cmd, mconfig, "User-Agent", SETENVIF_REQUEST, args);
>  }
>  
>  static const command_rec setenvif_module_cmds[] =
> @@ -444,7 +474,7 @@
>   * signal which call it is by having the earlier one pass a flag to the
>   * later one.
>   */
> -static int match_headers(request_rec *r)
> +static int match_headers(request_rec *r, int mode)
>  {
>      sei_cfg_rec *sconf;
>      sei_entry *entries;
> @@ -454,7 +484,14 @@
>      int i, j;
>      char *last_name;
>      ap_regmatch_t regm[AP_MAX_REG_MATCH];
> -
> +    apr_table_t *headers;
> +    
> +    if(SETENVIF_RESPONSE == mode) {
> +        headers = r->headers_out;
> +    } else {
> +        headers = r->headers_in;
> +    }
> +    
>      if (!ap_get_module_config(r->request_config, &setenvif_module)) {
>          ap_set_module_config(r->request_config, &setenvif_module,
>                               SEI_MAGIC_HEIRLOOM);
> @@ -468,9 +505,17 @@
>      entries = (sei_entry *) sconf->conditionals->elts;
>      last_name = NULL;
>      val = NULL;
> +    
>      for (i = 0; i < sconf->conditionals->nelts; ++i) {
>          sei_entry *b = &entries[i];
> -
> +        
> +        if(b->mode != mode) {
> +            continue;
> +        }
> +        
> +        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
> +                     "setenvif: trying %s", b->name);
> +        
>          /* Optimize the case where a bunch of directives in a row use the
>           * same header.  Remember we don't need to strcmp the two header
>           * names because we made sure the pointers were equal during
> @@ -498,6 +543,10 @@
>              case SPECIAL_REQUEST_PROTOCOL:
>                  val = r->protocol;
>                  break;
> +                /*we only set this in config in !RESPONSE*/
> +            case SPECIAL_CONTENT_TYPE:
> +                val = r->content_type;
> +                break;
>              case SPECIAL_NOT:
>                  if (b->pnamereg) {
>                      /* Matching headers_in against a regex. Iterate through
> @@ -505,7 +554,7 @@
>                       * headers.
>                       */
>                      const apr_array_header_t
> -                        *arr = apr_table_elts(r->headers_in);
> +                        *arr = apr_table_elts(headers);
>  
>                      elts = (const apr_table_entry_t *) arr->elts;
>                      val = NULL;
> @@ -517,7 +566,7 @@
>                  }
>                  else {
>                      /* Not matching against a regex */
> -                    val = apr_table_get(r->headers_in, b->name);
> +                    val = apr_table_get(headers, b->name);
>                      if (val == NULL) {
>                          val = apr_table_get(r->subprocess_env, b->name);
>                      }
> @@ -537,12 +586,19 @@
>              val_len = 0;
>          }
>  
> +         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
> +                      "setenvif: val =  %s", val);
> +
> +         
>          if ((b->pattern && apr_strmatch(b->pattern, val, val_len)) ||
>              (!b->pattern && !ap_regexec(b->preg, val, AP_MAX_REG_MATCH, regm,
>                                          0))) {
>              const apr_array_header_t *arr = apr_table_elts(b->features);
>              elts = (const apr_table_entry_t *) arr->elts;
> -
> +            
> +            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
> +                         "setenvif: matched: %s, %s", b->name, val);
> +            
>              for (j = 0; j < arr->nelts; ++j) {
>                  if (*(elts[j].val) == '!') {
>                      apr_table_unset(r->subprocess_env, elts[j].key);
> @@ -568,10 +624,57 @@
>      return DECLINED;
>  }
>  
> +static int setenvif_request(request_rec *r)
> +{
> +    return match_headers(r, SETENVIF_REQUEST);
> +
> +}
> +static apr_status_t setenvif_output_filter(ap_filter_t *f,
> +                                             apr_bucket_brigade *in)
> +{
> +    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, f->r->server,
> +                 "setenvif: setenvif_output_filter()");
> +
> +    ap_set_module_config(f->r->request_config, &setenvif_module,
> +                         NULL);
> +    match_headers(f->r, SETENVIF_RESPONSE);
> +    /*have to run twice, once to get server, then dir*/
> +    match_headers(f->r, SETENVIF_RESPONSE);
> +    /* remove ourselves from the filter chain */
> +    ap_remove_output_filter(f);
> +
> +    /* send the data up the stack */
> +    return ap_pass_brigade(f->next,in);
> +}
> +
> +static void setenvif_insert_output_filter(request_rec *r)
> +{
> +    sei_cfg_rec *sconf;
> +    int count = 0;
> +
> +    /*only add it if we have some*/
> +    sconf  = (sei_cfg_rec *) ap_get_module_config(r->server->module_config, 
> +                                                  &setenvif_module); 
> +    count += sconf->conditionals->nelts;
> +    
> +    sconf = (sei_cfg_rec *) ap_get_module_config(r->per_dir_config,
> +                                                 &setenvif_module);
> +    count += sconf->conditionals->nelts;
> +    
> +    if(count) {
> +        ap_add_output_filter_handle(setenvif_output_filter_handle, NULL, r,
> +                                    r->connection);
> +    } 
> +}
> +
>  static void register_hooks(apr_pool_t *p)
>  {
> -    ap_hook_header_parser(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
> -    ap_hook_post_read_request(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
> +    ap_hook_header_parser(setenvif_request, NULL, NULL, APR_HOOK_MIDDLE);
> +    ap_hook_post_read_request(setenvif_request, NULL, NULL, APR_HOOK_MIDDLE);
> +    setenvif_output_filter_handle = ap_register_output_filter("SETENVIF_OUT", 
> +                                                              setenvif_output_filter,
> +                                                              NULL, AP_FTYPE_CONTENT_SET); 
> +    ap_hook_insert_filter(setenvif_insert_output_filter, NULL, NULL, APR_HOOK_LAST);
>  }
>  
>  module AP_MODULE_DECLARE_DATA setenvif_module =


-- 
Brian Akins
Chief Operations Engineer
Turner Digital Media Technologies

Re: [PATCH] setenvif filter

Posted by Brian Akins <br...@turner.com>.
Here's a newer version with some special handling for content-type.  In 
response headers, we need to match r->content_type rather than the header.


-- 
Brian Akins
Lead Systems Engineer
CNN Internet Technologies

Re: [PATCH] setenvif filter

Posted by Brian Akins <br...@turner.com>.
Francois Pesce wrote:
> These patches may fix the r->content_type behaviour. Are you OK with it ?


+1



-- 
Brian Akins
Lead Systems Engineer
CNN Internet Technologies

Re: [PATCH] setenvif filter

Posted by Brian Akins <br...@turner.com>.
Anyone else care to vote on this so it can get, possibly, committed?


Francois Pesce wrote:
> These patches may fix the r->content_type behaviour. Are you OK with it ?
> 
> --
> *Francois Pesce*
> 
> 2006/5/31, Brian Akins <br...@turner.com>:
>  > Francois PESCE wrote:
>  > > I've discussed about a patch for mod_setenvif 2 years ago, and have
>  > > coded it at that time, it is successfully used on various host in
>  > > production since.
>  >
>  >
>  > You need to handle content type specially by checking r->content_type.
>  > For some reason, just doing apr_table_get(r->headers_out,
>  > "Content-type") would be null, but content_type would be set.
>  >
>  > See the patch I posted a few days ago.
>  >
>  >
>  > +1 in concept
>  >
>  > --
>  > Brian Akins
>  > Lead Systems Engineer
>  > CNN Internet Technologies
>  >
> 


-- 
Brian Akins
Lead Systems Engineer
CNN Internet Technologies

Re: [PATCH] setenvif filter

Posted by Francois Pesce <fr...@gmail.com>.
These patches may fix the r->content_type behaviour. Are you OK with it ?

-- 
*Francois Pesce*

2006/5/31, Brian Akins <br...@turner.com>:
> Francois PESCE wrote:
> > I've discussed about a patch for mod_setenvif 2 years ago, and have
> > coded it at that time, it is successfully used on various host in
> > production since.
>
>
> You need to handle content type specially by checking r->content_type.
> For some reason, just doing apr_table_get(r->headers_out,
> "Content-type") would be null, but content_type would be set.
>
> See the patch I posted a few days ago.
>
>
> +1 in concept
>
> --
> Brian Akins
> Lead Systems Engineer
> CNN Internet Technologies
>

Re: [PATCH] setenvif filter

Posted by Matthieu Estrade <me...@apache.org>.
Brian Akins wrote:
> Francois PESCE wrote:
>> I've discussed about a patch for mod_setenvif 2 years ago, and have
>> coded it at that time, it is successfully used on various host in
>> production since.
>
>
> You need to handle content type specially by checking r->content_type. 
> For some reason, just doing apr_table_get(r->headers_out, 
> "Content-type") would be null, but content_type would be set.
>
> See the patch I posted a few days ago.
>
>
> +1 in concept
>
This patch is used by many of our customers, in production environment.
+1


Re: [PATCH] setenvif filter

Posted by Brian Akins <br...@turner.com>.
Francois PESCE wrote:
> I've discussed about a patch for mod_setenvif 2 years ago, and have
> coded it at that time, it is successfully used on various host in
> production since.


You need to handle content type specially by checking r->content_type. 
For some reason, just doing apr_table_get(r->headers_out, 
"Content-type") would be null, but content_type would be set.

See the patch I posted a few days ago.


+1 in concept

-- 
Brian Akins
Lead Systems Engineer
CNN Internet Technologies