たまにはCTOっぽい仕事をしてみる

先日から色々とボヤいている、IO分散を行えるRewriteについて。

既存のRewriteEngineのRewriteRuleだと、

RewriteRule ^/images/(\d+)(.+)$ http://host/images/hoge/$1/$2 [R=301,L]

など、正規表現でのRewriteには都合が良いのだけど、
ディレクトリ分割には都合が悪い。

RewriteRule ^/images/(\d\d)(\d\d)(.+)$ http://host/images/hoge/$1/$2/$1$2$3 [R=301,L]

みたいな書き方もできなくないけど、桁数変わったりすると面倒…。
なので、結局自作してしまった。

SanzeroSplitEngin On
SanzeroSplitRule ^/images/(\d+)(.+)$ http://host/images/%SanzeroSplit%/$1$2 $1 5000 MOVED_PERMANENTLY

として動くもので、RewriteEngineよりも先に動く。

最初の引数はパスの正規表現。
2番目は%SanzeroSplit%の箇所に分割ディレクトリが入る、置換後のパスやURL。
3番目は最初の正規表現からIDにあたる部分。
4番目はIDを何分割するか。
5番目は次のように設定。

MOVED_PERMANENTLY => 301リダイレクトで終了。
MOVED_TEMPORARILY => 302リダイレクトで終了。
LAST => URLを変換して終了(これより後の処理とRewriteEngineは無視)。
PASS_THROUGH => URLを変換して継続(変換後のものがRewriteEngineで処理可能)。

わずか279行で実装できているので、
みんなもっとこういうの活用すればいいのになぁと。
そのうちNingxのモジュールも作ってみたいなぁ。

ソースはこんな感じ↓

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"

#include "apr_strings.h"
#include "apr_lib.h"

#define SANZERO_SPLIT_DISABLED        0
#define SANZERO_SPLIT_ENABLED         1

typedef struct {
  int state;
  apr_array_header_t *rules;
} sanzero_split_server_conf;

typedef struct {
  ap_regex_t *reg;
  char *output;
  char *id_str;
  int split;
  char *mode;
} sanzero_split_rule_conf;

module AP_MODULE_DECLARE_DATA sanzero_split_module;

static void parse(char **str, char **arg)
{
  char quote;

  while (apr_isspace(**str)) {
    ++**str;
  }

  quote = (**str == '"' || **str == '\'') ? **str++ : '\0';
  *arg = *str;

  for (; **str; ++*str) {
    if ((apr_isspace(**str) && !quote) || (**str == quote)) {
      break;
    }

    if (**str == '\\' && apr_isspace(*str[1])) {
      ++*str;
      continue;
    }
  }
}

static int parse_args(char *str, char **arg1, char **arg2, char **arg3, char **arg4, char **arg5)
{
  parse(&str, arg1);

  if (!*str) {
    return 1;
  }

  *str++ = '\0';

  parse(&str, arg2);

  if (!*str) {
    return 1;
  }

  *str++ = '\0';

  parse(&str, arg3);

  if (!*str) {
    return 1;
  }

  *str++ = '\0';

  parse(&str, arg4);

  if (!*str) {
    return 1;
  }

  *str++ = '\0';

  parse(&str, arg5);

  *str = '\0';

  return 0;
}

static int sanzero_split_translate_name(request_rec *r)
{
  //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", r->uri);
  sanzero_split_server_conf *sconf;

  sconf = ap_get_module_config(r->server->module_config, &sanzero_split_module);

  if (sconf->state == SANZERO_SPLIT_DISABLED) {
    return DECLINED;
  }

  sanzero_split_rule_conf *rules = (sanzero_split_rule_conf *)sconf->rules->elts;
  sanzero_split_rule_conf *rule;

  char *mode;
  char *filepath = apr_pstrdup(r->pool, r->uri);
  int i = 0;
  int id = 0;
  int split = 0;

  ap_regmatch_t reg_array[4];
  ap_regex_t *reg = ap_pregcomp(r->pool, "^(.+)(%SanzeroSplit%)(.+)$", AP_REG_EXTENDED);

  for (i = 0; i < sconf->rules->nelts; i ++) {
    rule = &rules[i];
    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "rule:%d, filepath:%s output:%s id_str:%s split:%d max:%d", i, filepath, rule->output, rule->id_str, rule->split, AP_MAX_REG_MATCH);

    ap_regmatch_t rule_reg_array[AP_MAX_REG_MATCH];

    if (ap_regexec(rule->reg, filepath, AP_MAX_REG_MATCH, rule_reg_array, 0) == 0) {
      id = atoi(ap_pregsub(r->pool, rule->id_str, filepath, AP_MAX_REG_MATCH, rule_reg_array));
      filepath = ap_pregsub(r->pool, rule->output, filepath, AP_MAX_REG_MATCH, rule_reg_array);

      if (id != 0 && rule->split != 0) {
        split = id % rule->split;
      }

      char *split_str = apr_palloc(r->pool, 64);
      apr_snprintf(split_str, 64, "%d", split);

      if (ap_regexec(reg, filepath, 4, reg_array, 0) == 0) {
        filepath = apr_pstrcat(r->pool, ap_pregsub(r->pool, "$1", filepath, 4, reg_array), split_str, ap_pregsub(r->pool, "$3", filepath, 4, reg_array), NULL);
      }

      mode = apr_pstrdup(r->pool, rule->mode);

      if (strcmp(mode, "MOVED_TEMPORARILY") == 0 || strcmp(mode, "MOVED_PERMANENTLY") == 0 || strcmp(mode, "LAST") == 0) {
        break;
      }
    }
  }

  ap_pregfree(r->pool, reg);

  if (strcmp(r->uri, filepath) == 0) {
    return DECLINED;
  }
  else {
    if (strcmp(mode, "MOVED_TEMPORARILY") == 0 || strcmp(mode, "MOVED_PERMANENTLY") == 0) {
      ap_regmatch_t http_reg_array[1];
      ap_regex_t *http_reg = ap_pregcomp(r->pool, "^https?://", AP_REG_EXTENDED);

      if (ap_regexec(http_reg, filepath, 1, http_reg_array, 0) == 0) {
        apr_table_setn(r->headers_out, "Location", filepath);
      }
      else {
        char *url = apr_pstrcat(r->pool, "http://", ap_get_server_name(r), filepath, NULL);
        apr_table_setn(r->headers_out, "Location", url);
      }

      ap_pregfree(r->pool, http_reg);

      if (strcmp(mode, "MOVED_PERMANENTLY") == 0) {
        return HTTP_MOVED_PERMANENTLY;
      }

      return HTTP_MOVED_TEMPORARILY;
    }
    else if (strcmp(mode, "LAST") == 0) {
      apr_finfo_t statbuf;

      if (apr_stat(&statbuf, filepath, APR_FINFO_MIN, r->pool) == APR_SUCCESS) {
        r->filename = filepath;
        r->finfo = statbuf;

        return OK;
      }
      else {
        return HTTP_NOT_FOUND;
      }
    }
    else if (strcmp(mode, "PASS_THROUGH") == 0) {
      r->uri = filepath;
      return DECLINED;
    }
  }

  return DECLINED;
}

static const char *sanzero_split_engine(cmd_parms *cmd, void *in_dconfig, int bool)
{
  sanzero_split_server_conf *sconf;

  sconf = ap_get_module_config(cmd->server->module_config, &sanzero_split_module);

  if (bool != 0) {
    sconf->state = SANZERO_SPLIT_ENABLED;
  }
  else {
    sconf->state = SANZERO_SPLIT_DISABLED;
  }

  return NULL;
}

static const char *sanzero_split_rule(cmd_parms *cmd, void *in_dconfig, const char *args)
{
  sanzero_split_server_conf *sconf;
  sanzero_split_rule_conf *rule;

  char *args_str;
  char *arg1;
  char *arg2;
  char *arg3;
  char *arg4;
  char *arg5;
  ap_regex_t *reg;

  sconf = ap_get_module_config(cmd->server->module_config, &sanzero_split_module);
  rule = apr_array_push(sconf->rules);

  args_str = apr_pstrdup(cmd->pool, args);

  if (parse_args(args_str, &arg1, &arg2, &arg3, &arg4, &arg5) != 0) {
    return apr_pstrcat(cmd->pool, "SanzeroSplit: bad argument line '", args, "'", NULL);
  }

  reg = ap_pregcomp(cmd->pool, arg1, AP_REG_EXTENDED);

  if (!reg) {
    return apr_pstrcat(cmd->pool, "SanzeroSplit: bad argument line '", args, "'", NULL);
  }

  rule->reg = reg;
  rule->output = arg2;
  rule->id_str = arg3;
  rule->split = atoi(arg4);
  rule->mode = arg5;

  return NULL;
}

static void *create_server_config(apr_pool_t *p, server_rec *s)
{
  sanzero_split_server_conf *sconf;

  sconf = (sanzero_split_server_conf *)apr_pcalloc(p, sizeof(sanzero_split_server_conf));

  sconf->state    = SANZERO_SPLIT_DISABLED;
  sconf->rules    = apr_array_make(p, 0, sizeof(sanzero_split_rule_conf));

  return (void *)sconf;
}

static void register_hooks(apr_pool_t *p)
{
  // mod_rewriteがAPR_HOOK_FIRST なので、それより前にhookさせる
  ap_hook_translate_name(sanzero_split_translate_name, NULL, NULL, APR_HOOK_REALLY_FIRST);
}

static const command_rec command_table[] =
{
  AP_INIT_FLAG("SanzeroSplitEngine", sanzero_split_engine, NULL, RSRC_CONF, "On or Off to enable or disable (default) the whole sanzero split engine"),
  AP_INIT_RAW_ARGS("SanzeroSplitRule", sanzero_split_rule, NULL, RSRC_CONF, "Split Rewrite Rule"),
  { NULL }
};

module AP_MODULE_DECLARE_DATA sanzero_split_module =
{
  STANDARD20_MODULE_STUFF,
  NULL,                        /* create per-dir config */
  NULL,                        /* merge per-dir config */
  create_server_config,        /* server config */
  NULL,                        /* merge server config */
  command_table,               /* command apr_table_t */
  register_hooks               /* register hooks */
};

ちなみに、この本はもう絶版らしい…。

apache

今さらながらポインタをちゃんと勉強した(備忘録)

最近ちょこちょこapacheモジュールなどを書いている。

実はC言語はそんなに使ったことがないので、
細かな部分は他のモジュールのコードなどを参照しつつ、
自分なりに理解して書いていたのだけど…。

mod_rewriteのソースを読んでいて、
RewriteRuleに渡すパラメータの処理について、
「ん?」と思うコードが。

static int parseargline(char *str, char **a1, char **a2, char **a3)
{
    char quote;

    while (apr_isspace(*str)) {
        ++str;
    }

    /*
     * determine first argument
     */
    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a1 = str;

    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }

    if (!*str) {
        return 1;
    }
    *str++ = '\0';

    while (apr_isspace(*str)) {
        ++str;
    }

    /*
     * determine second argument
     */
    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a2 = str;

    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }

    if (!*str) {
        *a3 = NULL; /* 3rd argument is optional */
        return 0;
    }
    *str++ = '\0';

    while (apr_isspace(*str)) {
        ++str;
    }

    if (!*str) {
        *a3 = NULL; /* 3rd argument is still optional */
        return 0;
    }

    /*
     * determine third argument
     */
    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a3 = str;
    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }
    *str = '\0';

    return 0;
}

スペースで区切って、パラメータを複数の変数を格納していることは
わかるのだけど、なんかカッコ悪い。

    while (apr_isspace(*str)) {
        ++str;
    }

    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a1 = str;

    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }

この部分が何度も呼ばれているので、一つにまとめたいなぁ。と思わない?
そこで色々調べてみた。

元のコードは

char *str = apr_pstrdup(cmd->pool, in_str);
char *a1;
char *a2;
char *a3;

parseargline(str, &a1, &a2, &a3);

のような形で呼ばれている。
そこでポインタについてちゃんと調べてみた。
詳細は置いておくとして、自分の中ではこう理解することにする。

char *str; などの宣言のデータの扱いについて、

&str アドレス
str ポインタ
*str 値

func(&str); と呼び出した関数の中、
func(char **str2); とした中では、

&str2 ポインタのポインタのアドレス
str2 ポインタのポインタ
*str2 ポインタ

関数の中で *str2 を操作することによって、
str のポインタを変更できる、参照渡しのようなことができるわけだ。

で、このコードをもう一度考えてみる。

    while (apr_isspace(*str)) {
        ++str;
    }

    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a1 = str;

    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }

この部分を

static void parse(char **str, char **arg)
{
  char quote;

  while (apr_isspace(**str)) {
    ++**str;
  }

  quote = (**str == '"' || **str == '\'') ? **str++ : '\0';
  *arg = *str;

  for (; **str; ++*str) {
    if ((apr_isspace(**str) && !quote) || (**str == quote)) {
      break;
    }

    if (**str == '\\' && apr_isspace(*str[1])) {
      ++*str;
      continue;
    }
  }
}

parse(&str, a1);

などのようにしてあげると、大分楽になるなぁと。

読んでも全く面白くないエントリーだと思うけど、
しばらく書かないと忘れそうなので、自分への備忘録として。