static mrb_value ngx_http_mruby_variable_get(mrb_state *mrb, mrb_value self, const char *c_name)
{
    ngx_http_request_t        *r;
    ngx_http_variable_value_t *var;
    ngx_str_t                  ngx_name;
    u_char                    *low;
    size_t                     len;
    ngx_uint_t                 key;

    r = ngx_http_mruby_get_request();

    ngx_name.len  = ngx_strlen(c_name);
    ngx_name.data = (u_char *)c_name;
    len           = ngx_name.len;

    if (len) {
        low = ngx_pnalloc(r->pool, len);
        if (low == NULL) {
            return mrb_nil_value();
        }
    } else {
        return mrb_nil_value();
    }

    key = ngx_hash_strlow(low, ngx_name.data, len);
    var = ngx_http_get_variable(r, &ngx_name, key);

    if (var->not_found) {
        return mrb_nil_value();
    }

    return mrb_str_new(mrb, (char *)var->data, var->len);
}
static mrb_value ngx_http_mruby_return(mrb_state *mrb, mrb_value self)
{
    ngx_http_mruby_rputs_chain_list_t *chain;
    ngx_http_mruby_ctx_t       *ctx;
    ngx_http_request_t         *r;
    mrb_int                     status;

    r      = ngx_http_mruby_get_request();
    status = NGX_HTTP_OK;

    mrb_get_args(mrb, "i", &status);

    r->headers_out.status = status;

    ctx   = ngx_http_get_module_ctx(r, ngx_http_mruby_module);
    chain = ctx->rputs_chain;

    if (chain != NULL) {
        (*chain->last)->buf->last_buf = 1;
    }

    if (r->headers_out.status == NGX_HTTP_OK && chain == NULL) {
        ngx_log_error(NGX_LOG_ERR
                      , r->connection->log
                      , 0
                      , "%s ERROR %s: Nginx.rputs is required in advance of Nginx.return(200)"
                      , MODULE_NAME
                      , __FUNCTION__
                      );
        r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    return self;
}
static mrb_value ngx_http_mruby_rputs(mrb_state *mrb, mrb_value self)
{
    mrb_value                   argv;
    ngx_buf_t                  *b;
    ngx_http_mruby_rputs_chain_list_t *chain;
    u_char                     *str;
    ngx_str_t                   ns;
    ngx_http_request_t         *r;
    ngx_http_mruby_ctx_t       *ctx;

    r   = ngx_http_mruby_get_request();
    ctx = ngx_http_get_module_ctx(r, ngx_http_mruby_module);

    mrb_get_args(mrb, "o", &argv);
    argv = mrb_obj_as_string(mrb, argv);

    ns.data = (u_char *)RSTRING_PTR(argv);
    ns.len  = RSTRING_LEN(argv);

    if (ns.len == 0) {
        return self;
    }

    if (ctx->rputs_chain == NULL) {
        chain       = ngx_pcalloc(r->pool, sizeof(ngx_http_mruby_rputs_chain_list_t));
        chain->out  = ngx_alloc_chain_link(r->pool);
        chain->last = &chain->out;
    } else {
        chain = ctx->rputs_chain;
        (*chain->last)->next = ngx_alloc_chain_link(r->pool);
        chain->last = &(*chain->last)->next;
    }

    b = ngx_calloc_buf(r->pool);
    (*chain->last)->buf = b;
    (*chain->last)->next = NULL;

    if ((str = ngx_pnalloc(r->pool, ns.len + 1)) == NULL) {
        return self;
    }

    ngx_memcpy(str, ns.data, ns.len);
    str[ns.len] = '\0';
    (*chain->last)->buf->pos    = str;
    (*chain->last)->buf->last   = str + ns.len;
    (*chain->last)->buf->memory = 1;
    ctx->rputs_chain = chain;

    if (r->headers_out.content_length_n == -1) {
        r->headers_out.content_length_n += ns.len + 1;
    } else {
        r->headers_out.content_length_n += ns.len;
    }

    return self;
}
static mrb_value ngx_http_mruby_log(mrb_state *mrb, mrb_value self)
{   
    mrb_value          *argv;
    mrb_value           msg;
    mrb_int             argc;
    mrb_int             log_level;
    ngx_http_request_t *r;

    r = ngx_http_mruby_get_request();

    mrb_get_args(mrb, "*", &argv, &argc);

    if (argc != 2) {
        ngx_log_error(NGX_LOG_ERR
            , r->connection->log
            , 0
            , "%s ERROR %s: argument is not 2"
            , MODULE_NAME
            , __FUNCTION__
        );
        return self;
    }

    if (mrb_type(argv[0]) != MRB_TT_FIXNUM) {
        ngx_log_error(NGX_LOG_ERR
            , r->connection->log
            , 0
            , "%s ERROR %s: argv[0] is not integer"
            , MODULE_NAME
            , __FUNCTION__
        );
        return self;
    }

    log_level = mrb_fixnum(argv[0]);

    if (log_level < 0) {
        ngx_log_error(NGX_LOG_ERR
            , r->connection->log
            , 0
            , "%s ERROR %s: log level is not positive number"
            , MODULE_NAME
            , __FUNCTION__
        );
        return self;
    }

    msg = mrb_obj_as_string(mrb, argv[1]);

    ngx_log_error((ngx_uint_t)log_level, r->connection->log, 0, "%s", RSTRING_PTR(msg));

    return self;
}
static mrb_value ngx_http_mruby_variable_set_internal(mrb_state *mrb, mrb_value self, char *k, mrb_value o)
{
    ngx_http_request_t        *r;
    ngx_http_variable_t       *v;
    ngx_http_variable_value_t *vv;
    ngx_http_core_main_conf_t *cmcf;
    ngx_str_t                  key;
    ngx_uint_t                 hash;
    u_char                    *val, *low;

    r = ngx_http_mruby_get_request();

    val      = (u_char *)RSTRING_PTR(o);
    key.data = (u_char *)k;
    key.len  = ngx_strlen(k);

    if (key.len) {
        low = ngx_pnalloc(r->pool, key.len);

        if (low == NULL) {
            return mrb_nil_value();
        }
    } else {
        return mrb_nil_value();
    }

    hash = ngx_hash_strlow(low, key.data, key.len);
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    v    = ngx_hash_find(&cmcf->variables_hash, hash, key.data, key.len);

    if (v) {
        if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
            ngx_log_error(NGX_LOG_ERR
                , r->connection->log
                , 0
                , "%s ERROR %s:%d: %s not changeable"
                , MODULE_NAME
                , __FUNCTION__
                , __LINE__
                , key.data
            );
            return mrb_nil_value();
        }
        if (v->set_handler) {
            vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));

            if (vv == NULL) {
                ngx_log_error(NGX_LOG_ERR
                    , r->connection->log
                    , 0
                    , "%s ERROR %s:%d: memory allocate failed"
                    , MODULE_NAME
                    , __FUNCTION__
                    , __LINE__
                );
                return mrb_nil_value();
            }

            vv->valid        = 1;
            vv->not_found    = 0;
            vv->no_cacheable = 0;
            vv->data         = val;
            vv->len          = RSTRING_LEN(o);

            v->set_handler(r, vv, v->data);

            return mrb_str_new_cstr(mrb, (char *)val);
        }
        if (v->flags & NGX_HTTP_VAR_INDEXED) {
            vv = &r->variables[v->index];

            vv->valid        = 1;
            vv->not_found    = 0;
            vv->no_cacheable = 0;
            vv->data         = val;
            vv->len          = RSTRING_LEN(o);

            return mrb_str_new_cstr(mrb, (char *)val);
        }
        ngx_log_error(NGX_LOG_ERR
            , r->connection->log
            , 0
            , "%s ERROR %s:%d: %s is not assinged"
            , MODULE_NAME
            , __FUNCTION__
            , __LINE__
            , key.data
        );
        return mrb_nil_value();
    }

    ngx_log_error(NGX_LOG_ERR
        , r->connection->log
        , 0
        , "%s ERROR %s:%d: %s is not found"
        , MODULE_NAME
        , __FUNCTION__
        , __LINE__
        , key.data
    );
    return mrb_nil_value();
}
// like Nginx rewrite keywords
// used like this:
// => http code 3xx location in browser
// => internal redirection in nginx
static mrb_value ngx_http_mruby_redirect(mrb_state *mrb, mrb_value self)
{
    int                   argc;
    u_char               *str;
    ngx_int_t             rc;
    mrb_value             uri, code;
    ngx_str_t             ns;
    ngx_http_request_t   *r;
    ngx_http_mruby_ctx_t *ctx;

    r    = ngx_http_mruby_get_request();
    ctx  = ngx_http_get_module_ctx(r, ngx_http_mruby_module);
    argc = mrb_get_args(mrb, "o|oo", &uri, &code);

    if (argc == 2) {
        rc = mrb_fixnum(code);
    } else {
        rc = NGX_HTTP_MOVED_TEMPORARILY;
    }

    uri = mrb_obj_as_string(mrb, uri);

    // save location uri to ns
    ns.data = (u_char *)RSTRING_PTR(uri);
    ns.len  = RSTRING_LEN(uri);

    if (ns.len == 0) {
        return mrb_nil_value();
    }

    // if uri start with scheme prefix
    // return 3xx for redirect
    // else generate a internal redirection and response to raw request
    // request.path is not changed
    if (ngx_strncmp(ns.data,    "http://",  sizeof("http://") - 1) == 0 
        || ngx_strncmp(ns.data, "https://", sizeof("https://") - 1) == 0 
        || ngx_strncmp(ns.data, "$scheme",  sizeof("$scheme") - 1) == 0) {    

        if ((str = ngx_pnalloc(r->pool, ns.len + 1)) == NULL) {
            return self;
        }

        ngx_memcpy(str, ns.data, ns.len);
        str[ns.len] = '\0';

        // build redirect location
        r->headers_out.location = ngx_list_push(&r->headers_out.headers);

        if (r->headers_out.location == NULL) {
            return self;
        }

        r->headers_out.location->hash =
                ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash(
                         ngx_hash('l', 'o'), 'c'), 'a'), 't'), 'i'), 'o'), 'n');

        r->headers_out.location->value.data = ns.data;
        r->headers_out.location->value.len  = ns.len;
        ngx_str_set(&r->headers_out.location->key, "Location");
        r->headers_out.status = rc;

        ctx->exited    = 1;
        ctx->exit_code = rc;
    } else {
        ctx->exited    = 1;
        ctx->exit_code = ngx_http_internal_redirect(r, &ns, &r->args);
    }

    return self;
}