Friday, July 26, 2013

Including a Request Via Apache Module

The first step in including another URL into an apache request is to create an output filter that simply does nothing but append the content to a variable. This filter must not be registered except in very specific circumstances.

Please note that this function is copied directly from some private code. The ((template_context *)f->ctx) variable is a standard filter context variable that is typecast to what I know it is, which contains an apr_bucket, etc (and my variable that includes the output of the "include"). The structure (so you can know what type the variables are) will be at the bottom of this document. The function :
    static apr_status_t mod_template_include_output_filter(ap_filter_t *f, apr_bucket_brigade *bb) {
      for (((template_context *)f->ctx)->include_bucket = APR_BRIGADE_FIRST(bb);
          ((template_context *)f->ctx)->include_bucket != APR_BRIGADE_SENTINEL(bb);
          ((template_context *)f->ctx)->include_bucket = APR_BUCKET_NEXT(((template_context *)f->ctx)->include_bucket)) {    if (!APR_BUCKET_IS_EOS(((template_context *)f->ctx)->include_bucket)) {
          if (apr_bucket_read(((template_context *)f->ctx)->include_bucket,&(((template_context *)f->ctx)->include_data),&(((template_context *)f->ctx)->include_length),APR_BLOCK_READ) == APR_SUCCESS) {
            if (((template_context *)f->ctx)->include_data_real == NULL) {
              ((template_context *)f->ctx)->include_data_real = apr_palloc(f->r->pool,((template_context *)f->ctx)->include_length+1);
              strncpy(((template_context *)f->ctx)->include_data_real,((template_context *)f->ctx)->include_data,((template_context *)f->ctx)->include_length);
              ((template_context *)f->ctx)->include_data_real[((template_context *)f->ctx)->include_length] = '\0';
            } else {
              ((template_context *)f->ctx)->include_data_tmp = ((template_context *)f->ctx)->include_data_real;
              ((template_context *)f->ctx)->include_data_real = apr_palloc(f->r->pool,((template_context *)f->ctx)->include_length+strlen(((template_context *)f->ctx)->include_data_tmp) + 1);
              strcpy(((template_context *)f->ctx)->include_data_real,((template_context *)f->ctx)->include_data_tmp);
              strncat(((template_context *)f->ctx)->include_data_real,((template_context *)f->ctx)->include_data,((template_context *)f->ctx)->include_length);
              ((template_context *)f->ctx)->include_data_real[((template_context *)f->ctx)->include_length+strlen(((template_context *)f->ctx)->include_data_tmp)] = '\0';
            APR_BUCKET_REMOVE(((template_context *)f->ctx)->include_bucket);
      return APR_SUCCESS;
      //return apr_brigade_create(f->r->pool,f->c->bucket_alloc);

The next step is to actually call a sub request, assign the filter to it, and run it. First, I set up the request doing this :

    ((template_context *)f->ctx)->include_filter_rec = apr_palloc(f->r->pool,sizeof(ap_filter_rec_t));
      memset(((template_context *)f->ctx)->include_filter_rec,0,sizeof(ap_filter_rec_t));
      ((template_context *)f->ctx)->include_filter_rec->name = "TEMPLATE-INCLUDE-WRAPPER";
      ((template_context *)f->ctx)->include_filter_rec->filter_func.out_func = &mod_template_include_output_filter;
      ((template_context *)f->ctx)->include_filter_rec->next = NULL;
      ((template_context *)f->ctx)->include_filter_rec->ftype = AP_FTYPE_RESOURCE;
      ((template_context *)f->ctx)->include_filter = apr_palloc(f->r->pool,sizeof(ap_filter_t));
      ((template_context *)f->ctx)->include_filter->frec = ((template_context *)f->ctx)->include_filter_rec;
      ((template_context *)f->ctx)->include_filter->ctx = (template_context *)f->ctx;
      ((template_context *)f->ctx)->include_filter->next = NULL;
      ((template_context *)f->ctx)->include_filter->r = f->r;
      ((template_context *)f->ctx)->include_filter->c = f->r->connection;

Next, I run the request using :

    /* now, run the subrequest */
      ((template_context *)f->ctx)->include_data = NULL;
      ((template_context *)f->ctx)->include_data_real = NULL;
      ((template_context *)f->ctx)->include_r = ap_sub_req_lookup_uri(uri,f->r,((template_context *)f->ctx)->include_filter);
      if ((((template_context *)f->ctx)->include_r != NULL) && (((template_context *)f->ctx)->include_r->status == HTTP_OK)) {
        ((template_context *)f->ctx)->include_int = ap_run_sub_req(((template_context *)f->ctx)->include_r);
      if (((template_context *)f->ctx)->include_r != NULL) {
        ap_destroy_sub_req(((template_context *)f->ctx)->include_r);

Now, just to be sure, check the results - if there was an error, you may not want that in the document. For example :

    /* did we have an error of sorts? */
      if (((template_context *)f->ctx)->include_data_real == NULL) {
        return NULL;

Then you can create a new bucket (with a filter) or just respond with the handler using the fresh content :

    new_bucket = apr_bucket_pool_create(((template_context *)f->ctx)->include_data_real,strlen(((template_context *)f->ctx)->include_data_real),f->r->pool,f->c->bucket_alloc);

Now, for those that have waited patiently, the following is the structure definition of my context :

    typedef struct template_context {
      char                          *title;
      char                          *head;
      const char                    *include_data;
      char                          *include_data_tmp;
      char                          *include_data_real;
      apr_bucket                    *include_bucket;
    //  apr_off_t                   include_length;
      apr_ssize_t                   include_length;
      ap_filter_rec_t               *include_filter_rec;
      ap_filter_t                   *include_filter;
      int                           include_int;
      request_rec                   *include_r;
      const char                    *header;
      const char                    *trailer;
      const char                    *bucket_data;
      apr_file_t                    *f_header;
      apr_file_t                    *f_trailer;
      apr_file_t                    *file_tmp;
      apr_finfo_t                   sb;
      char                          *buffer;
      char                          *char_tmp;
      int                           get_tag_length;
      int                           int_tmp;
      int                           content_length;
      char                          *tag_open;
      char                          *tag_close;
      int                           flags;
      struct content_type_list      *types;
      const apr_strmatch_pattern    *strmatch;
      const char                    *match;
      apr_bucket_brigade            *brigade;
      apr_bucket                    *trailer_bucket;
      apr_size_t                    bucket_length;
      apr_bucket                    *current_bucket;
      apr_bucket                    *tmp_bucket;
      apr_bucket                    *new_bucket;
      apr_time_t                    time_tmp;
      template_conf                 *config;
    } template_context;

Yes, it will be a little obvious that my template wrapping module is fairly complex, but that is okay - it does a great deal more than just wrapping. It adjusts modified dates/times based on the template, it uses includes, finding additional components (dynamic menu building, etc). And all done on the fly. It was a very fun project to build!

No comments:

Post a Comment