summaryrefslogtreecommitdiffstats
path: root/debian/vendor-h2o/lib/handler/mruby
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--debian/vendor-h2o/lib/handler/mruby.c938
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/chunked.c270
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded.c.h111
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb35
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded/core.rb81
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb54
-rw-r--r--debian/vendor-h2o/lib/handler/mruby/http_request.c500
7 files changed, 1989 insertions, 0 deletions
diff --git a/debian/vendor-h2o/lib/handler/mruby.c b/debian/vendor-h2o/lib/handler/mruby.c
new file mode 100644
index 0000000..af2af53
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby.c
@@ -0,0 +1,938 @@
+/*
+ * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Ryosuke Matsumoto,
+ * Masayoshi Takahashi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <mruby.h>
+#include <mruby/proc.h>
+#include <mruby/array.h>
+#include <mruby/class.h>
+#include <mruby/compile.h>
+#include <mruby/error.h>
+#include <mruby/hash.h>
+#include <mruby/string.h>
+#include <mruby/throw.h>
+#include <mruby/variable.h>
+#include <mruby_input_stream.h>
+#include "h2o.h"
+#include "h2o/mruby_.h"
+#include "mruby/embedded.c.h"
+
+#define STATUS_FALLTHRU 399
+#define FALLTHRU_SET_PREFIX "x-fallthru-set-"
+
+#define FREEZE_STRING(v) MRB_SET_FROZEN_FLAG(mrb_obj_ptr(v))
+
+__thread h2o_mruby_generator_t *h2o_mruby_current_generator = NULL;
+
+void h2o_mruby__assert_failed(mrb_state *mrb, const char *file, int line)
+{
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ fprintf(stderr, "unexpected ruby error at file: \"%s\", line %d: %s", file, line, error->as.heap.ptr);
+ abort();
+}
+
+void h2o_mruby_setup_globals(mrb_state *mrb)
+{
+ const char *root = getenv("H2O_ROOT");
+ if (root == NULL)
+ root = H2O_TO_STR(H2O_ROOT);
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$H2O_ROOT"), mrb_str_new(mrb, root, strlen(root)));
+
+ h2o_mruby_eval_expr(mrb, "$LOAD_PATH << \"#{$H2O_ROOT}/share/h2o/mruby\"");
+ h2o_mruby_assert(mrb);
+
+ /* require core modules and include built-in libraries */
+ h2o_mruby_eval_expr(mrb, "require \"#{$H2O_ROOT}/share/h2o/mruby/preloads.rb\"");
+ if (mrb->exc != NULL) {
+ if (mrb_obj_is_instance_of(mrb, mrb_obj_value(mrb->exc), mrb_class_get(mrb, "LoadError"))) {
+ fprintf(stderr, "file \"%s/%s\" not found. Did you forget to run `make install` ?", root,
+ "share/h2o/mruby/preloads.rb");
+ } else {
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ fprintf(stderr, "an error occurred while loading %s/%s: %s", root, "share/h2o/mruby/preloads.rb", error->as.heap.ptr);
+ }
+ abort();
+ }
+}
+
+mrb_value h2o_mruby_to_str(mrb_state *mrb, mrb_value v)
+{
+ if (!mrb_string_p(v))
+ H2O_MRUBY_EXEC_GUARD({ v = mrb_str_to_str(mrb, v); });
+ return v;
+}
+
+mrb_value h2o_mruby_eval_expr(mrb_state *mrb, const char *expr)
+{
+ return mrb_funcall(mrb, mrb_top_self(mrb), "eval", 1, mrb_str_new_cstr(mrb, expr));
+}
+
+void h2o_mruby_define_callback(mrb_state *mrb, const char *name, int id)
+{
+ mrb_value args[2];
+ args[0] = mrb_str_new_cstr(mrb, name);
+ args[1] = mrb_fixnum_value(id);
+ mrb_funcall_argv(mrb, mrb_top_self(mrb), mrb_intern_lit(mrb, "_h2o_define_callback"), 2, args);
+
+ if (mrb->exc != NULL) {
+ fprintf(stderr, "failed to define mruby function: %s\n", name);
+ h2o_mruby_assert(mrb);
+ }
+}
+
+mrb_value h2o_mruby_create_data_instance(mrb_state *mrb, mrb_value class_obj, void *ptr, const mrb_data_type *type)
+{
+ struct RClass *klass = mrb_class_ptr(class_obj);
+ struct RData *data = mrb_data_object_alloc(mrb, klass, ptr, type);
+ return mrb_obj_value(data);
+}
+
+mrb_value h2o_mruby_compile_code(mrb_state *mrb, h2o_mruby_config_vars_t *config, char *errbuf)
+{
+ mrbc_context *cxt;
+ struct mrb_parser_state *parser;
+ struct RProc *proc = NULL;
+ mrb_value result = mrb_nil_value();
+
+ /* parse */
+ if ((cxt = mrbc_context_new(mrb)) == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ if (config->path != NULL)
+ mrbc_filename(mrb, cxt, config->path);
+ cxt->capture_errors = 1;
+ cxt->lineno = config->lineno;
+ if ((parser = mrb_parse_nstring(mrb, config->source.base, (int)config->source.len, cxt)) == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ /* return erro if errbuf is supplied, or abort */
+ if (parser->nerr != 0) {
+ if (errbuf == NULL) {
+ fprintf(stderr, "%s: internal error (unexpected state)\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ snprintf(errbuf, 256, "line %d:%s", parser->error_buffer[0].lineno, parser->error_buffer[0].message);
+ strcat(errbuf, "\n\n");
+ if (h2o_str_at_position(errbuf + strlen(errbuf), config->source.base, config->source.len,
+ parser->error_buffer[0].lineno - config->lineno + 1, parser->error_buffer[0].column) != 0) {
+ /* remove trailing "\n\n" in case we failed to append the source code at the error location */
+ errbuf[strlen(errbuf) - 2] = '\0';
+ }
+ goto Exit;
+ }
+ /* generate code */
+ if ((proc = mrb_generate_code(mrb, parser)) == NULL) {
+ fprintf(stderr, "%s: internal error (mrb_generate_code failed)\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+
+ /* adjust stack length of toplevel environment (see https://github.com/h2o/h2o/issues/1464#issuecomment-337880408) */
+ if (mrb->c->cibase->env) {
+ struct REnv *e = mrb->c->cibase->env;
+ if (MRB_ENV_STACK_LEN(e) < proc->body.irep->nlocals)
+ MRB_SET_ENV_STACK_LEN(e, proc->body.irep->nlocals);
+ }
+
+ /* reset configuration context */
+ h2o_mruby_eval_expr(mrb, "H2O::ConfigurationContext.reset");
+ h2o_mruby_assert(mrb);
+
+ /* run code and generate handler */
+ result = mrb_run(mrb, proc, mrb_top_self(mrb));
+ if (mrb->exc != NULL) {
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ snprintf(errbuf, 256, "%s", error->as.heap.ptr);
+ mrb->exc = 0;
+ result = mrb_nil_value();
+ goto Exit;
+ } else if (mrb_nil_p(result)) {
+ snprintf(errbuf, 256, "returned value is not callable");
+ goto Exit;
+ }
+
+ /* call post_handler_generation hooks */
+ mrb_funcall_argv(mrb, h2o_mruby_eval_expr(mrb, "H2O::ConfigurationContext.instance"),
+ mrb_intern_lit(mrb, "call_post_handler_generation_hooks"), 1, &result);
+ if (mrb->exc != NULL) {
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ snprintf(errbuf, 256, "%s", error->as.heap.ptr);
+ mrb->exc = 0;
+ result = mrb_nil_value();
+ goto Exit;
+ }
+
+Exit:
+ mrb_parser_free(parser);
+ mrbc_context_free(mrb, cxt);
+ return result;
+}
+
+static h2o_iovec_t convert_header_name_to_env(h2o_mem_pool_t *pool, const char *name, size_t len)
+{
+#define KEY_PREFIX "HTTP_"
+#define KEY_PREFIX_LEN (sizeof(KEY_PREFIX) - 1)
+
+ h2o_iovec_t ret;
+
+ ret.len = len + KEY_PREFIX_LEN;
+ ret.base = h2o_mem_alloc_pool(pool, ret.len);
+
+ memcpy(ret.base, KEY_PREFIX, KEY_PREFIX_LEN);
+
+ char *d = ret.base + KEY_PREFIX_LEN;
+ for (; len != 0; ++name, --len)
+ *d++ = *name == '-' ? '_' : h2o_toupper(*name);
+
+ return ret;
+
+#undef KEY_PREFIX
+#undef KEY_PREFIX_LEN
+}
+
+static mrb_value build_constants(mrb_state *mrb, const char *server_name, size_t server_name_len)
+{
+ mrb_value ary = mrb_ary_new_capa(mrb, H2O_MRUBY_NUM_CONSTANTS);
+ mrb_int i;
+
+ int gc_arena = mrb_gc_arena_save(mrb);
+
+ {
+ h2o_mem_pool_t pool;
+ h2o_mem_init_pool(&pool);
+ for (i = 0; i != H2O_MAX_TOKENS; ++i) {
+ const h2o_token_t *token = h2o__tokens + i;
+ mrb_value lit = mrb_nil_value();
+ if (token == H2O_TOKEN_CONTENT_TYPE) {
+ lit = mrb_str_new_lit(mrb, "CONTENT_TYPE");
+ } else if (token->buf.len != 0) {
+ h2o_iovec_t n = convert_header_name_to_env(&pool, token->buf.base, token->buf.len);
+ lit = mrb_str_new(mrb, n.base, n.len);
+ }
+ if (mrb_string_p(lit)) {
+ FREEZE_STRING(lit);
+ mrb_ary_set(mrb, ary, i, lit);
+ }
+ }
+ h2o_mem_clear_pool(&pool);
+ }
+
+#define SET_STRING(idx, value) \
+ do { \
+ mrb_value lit = (value); \
+ FREEZE_STRING(lit); \
+ mrb_ary_set(mrb, ary, idx, lit); \
+ } while (0)
+#define SET_LITERAL(idx, str) SET_STRING(idx, mrb_str_new_lit(mrb, str))
+
+ SET_LITERAL(H2O_MRUBY_LIT_REQUEST_METHOD, "REQUEST_METHOD");
+ SET_LITERAL(H2O_MRUBY_LIT_SCRIPT_NAME, "SCRIPT_NAME");
+ SET_LITERAL(H2O_MRUBY_LIT_PATH_INFO, "PATH_INFO");
+ SET_LITERAL(H2O_MRUBY_LIT_QUERY_STRING, "QUERY_STRING");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_NAME, "SERVER_NAME");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_ADDR, "SERVER_ADDR");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_PORT, "SERVER_PORT");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_PROTOCOL, "SERVER_PROTOCOL");
+ SET_LITERAL(H2O_MRUBY_LIT_CONTENT_LENGTH, "CONTENT_LENGTH");
+ SET_LITERAL(H2O_MRUBY_LIT_REMOTE_ADDR, "REMOTE_ADDR");
+ SET_LITERAL(H2O_MRUBY_LIT_REMOTE_PORT, "REMOTE_PORT");
+ SET_LITERAL(H2O_MRUBY_LIT_REMOTE_USER, "REMOTE_USER");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_URL_SCHEME, "rack.url_scheme");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_MULTITHREAD, "rack.multithread");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_MULTIPROCESS, "rack.multiprocess");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_RUN_ONCE, "rack.run_once");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_HIJACK_, "rack.hijack?");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_INPUT, "rack.input");
+ SET_LITERAL(H2O_MRUBY_LIT_RACK_ERRORS, "rack.errors");
+ SET_LITERAL(H2O_MRUBY_LIT_SERVER_SOFTWARE, "SERVER_SOFTWARE");
+ SET_STRING(H2O_MRUBY_LIT_SERVER_SOFTWARE_VALUE, mrb_str_new(mrb, server_name, server_name_len));
+ SET_LITERAL(H2O_MRUBY_LIT_SEPARATOR_COMMA, ", ");
+ SET_LITERAL(H2O_MRUBY_LIT_SEPARATOR_SEMICOLON, "; ");
+
+#undef SET_LITERAL
+#undef SET_STRING
+
+ h2o_mruby_eval_expr(mrb, H2O_MRUBY_CODE_CORE);
+ h2o_mruby_assert(mrb);
+
+ mrb_ary_set(mrb, ary, H2O_MRUBY_PROC_EACH_TO_ARRAY,
+ mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "_h2o_proc_each_to_array", 0));
+ h2o_mruby_assert(mrb);
+
+ /* sends exception using H2O_MRUBY_CALLBACK_ID_EXCEPTION_RAISED */
+ mrb_ary_set(mrb, ary, H2O_MRUBY_PROC_APP_TO_FIBER,
+ mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "_h2o_proc_app_to_fiber", 0));
+ h2o_mruby_assert(mrb);
+
+ mrb_gc_arena_restore(mrb, gc_arena);
+ return ary;
+}
+
+static h2o_mruby_shared_context_t *create_shared_context(h2o_context_t *ctx)
+{
+ /* init mruby in every thread */
+ h2o_mruby_shared_context_t *shared_ctx = h2o_mem_alloc(sizeof(*shared_ctx));
+ if ((shared_ctx->mrb = mrb_open()) == NULL) {
+ fprintf(stderr, "%s: no memory\n", H2O_MRUBY_MODULE_NAME);
+ abort();
+ }
+ h2o_mruby_setup_globals(shared_ctx->mrb);
+ shared_ctx->constants = build_constants(shared_ctx->mrb, ctx->globalconf->server_name.base, ctx->globalconf->server_name.len);
+ shared_ctx->symbols.sym_call = mrb_intern_lit(shared_ctx->mrb, "call");
+ shared_ctx->symbols.sym_close = mrb_intern_lit(shared_ctx->mrb, "close");
+ shared_ctx->symbols.sym_method = mrb_intern_lit(shared_ctx->mrb, "method");
+ shared_ctx->symbols.sym_headers = mrb_intern_lit(shared_ctx->mrb, "headers");
+ shared_ctx->symbols.sym_body = mrb_intern_lit(shared_ctx->mrb, "body");
+ shared_ctx->symbols.sym_async = mrb_intern_lit(shared_ctx->mrb, "async");
+
+ h2o_mruby_send_chunked_init_context(shared_ctx);
+ h2o_mruby_http_request_init_context(shared_ctx);
+
+ return shared_ctx;
+}
+
+static void dispose_shared_context(void *data)
+{
+ if (data == NULL)
+ return;
+ h2o_mruby_shared_context_t *shared_ctx = (h2o_mruby_shared_context_t *)data;
+ mrb_close(shared_ctx->mrb);
+ free(shared_ctx);
+}
+
+static h2o_mruby_shared_context_t *get_shared_context(h2o_context_t *ctx)
+{
+ static size_t key = SIZE_MAX;
+ void **data = h2o_context_get_storage(ctx, &key, dispose_shared_context);
+ if (*data == NULL) {
+ *data = create_shared_context(ctx);
+ }
+ return *data;
+}
+
+static void on_context_init(h2o_handler_t *_handler, h2o_context_t *ctx)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+ h2o_mruby_context_t *handler_ctx = h2o_mem_alloc(sizeof(*handler_ctx));
+
+ handler_ctx->handler = handler;
+ handler_ctx->shared = get_shared_context(ctx);
+
+ /* compile code (must be done for each thread) */
+ int arena = mrb_gc_arena_save(handler_ctx->shared->mrb);
+ mrb_value proc = h2o_mruby_compile_code(handler_ctx->shared->mrb, &handler->config, NULL);
+
+ handler_ctx->proc =
+ mrb_funcall_argv(handler_ctx->shared->mrb, mrb_ary_entry(handler_ctx->shared->constants, H2O_MRUBY_PROC_APP_TO_FIBER),
+ handler_ctx->shared->symbols.sym_call, 1, &proc);
+ h2o_mruby_assert(handler_ctx->shared->mrb);
+ mrb_gc_arena_restore(handler_ctx->shared->mrb, arena);
+ mrb_gc_protect(handler_ctx->shared->mrb, handler_ctx->proc);
+
+ h2o_context_set_handler_context(ctx, &handler->super, handler_ctx);
+}
+
+static void on_context_dispose(h2o_handler_t *_handler, h2o_context_t *ctx)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+ h2o_mruby_context_t *handler_ctx = h2o_context_get_handler_context(ctx, &handler->super);
+
+ if (handler_ctx == NULL)
+ return;
+
+ free(handler_ctx);
+}
+
+static void on_handler_dispose(h2o_handler_t *_handler)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+
+ free(handler->config.source.base);
+ free(handler->config.path);
+ free(handler);
+}
+
+static void report_exception(h2o_req_t *req, mrb_state *mrb)
+{
+ mrb_value obj = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
+ struct RString *error = mrb_str_ptr(obj);
+ h2o_req_log_error(req, H2O_MRUBY_MODULE_NAME, "mruby raised: %s\n", error->as.heap.ptr);
+ mrb->exc = NULL;
+}
+
+static void stringify_address(h2o_conn_t *conn, socklen_t (*cb)(h2o_conn_t *conn, struct sockaddr *), mrb_state *mrb,
+ mrb_value *host, mrb_value *port)
+{
+ struct sockaddr_storage ss;
+ socklen_t sslen;
+ char buf[NI_MAXHOST];
+
+ *host = mrb_nil_value();
+ *port = mrb_nil_value();
+
+ if ((sslen = cb(conn, (void *)&ss)) == 0)
+ return;
+ size_t l = h2o_socket_getnumerichost((void *)&ss, sslen, buf);
+ if (l != SIZE_MAX)
+ *host = mrb_str_new(mrb, buf, l);
+ int32_t p = h2o_socket_getport((void *)&ss);
+ if (p != -1) {
+ l = (int)sprintf(buf, "%" PRIu16, (uint16_t)p);
+ *port = mrb_str_new(mrb, buf, l);
+ }
+}
+
+static void on_rack_input_free(mrb_state *mrb, const char *base, mrb_int len, void *_input_stream)
+{
+ /* reset ref to input_stream */
+ mrb_value *input_stream = _input_stream;
+ *input_stream = mrb_nil_value();
+}
+
+static int build_env_sort_header_cb(const void *_x, const void *_y)
+{
+ const h2o_header_t *x = *(const h2o_header_t **)_x, *y = *(const h2o_header_t **)_y;
+ if (x->name->len < y->name->len)
+ return -1;
+ if (x->name->len > y->name->len)
+ return 1;
+ if (x->name->base != y->name->base) {
+ int r = memcmp(x->name->base, y->name->base, x->name->len);
+ if (r != 0)
+ return r;
+ }
+ assert(x != y);
+ /* the order of the headers having the same name needs to be retained */
+ return x < y ? -1 : 1;
+}
+
+static mrb_value build_path_info(mrb_state *mrb, h2o_req_t *req, size_t confpath_len_wo_slash)
+{
+ if (req->path_normalized.len == confpath_len_wo_slash)
+ return mrb_str_new_lit(mrb, "");
+
+ assert(req->path_normalized.len > confpath_len_wo_slash);
+
+ size_t path_info_start, path_info_end = req->query_at != SIZE_MAX ? req->query_at : req->path.len;
+
+ if (req->norm_indexes == NULL) {
+ path_info_start = confpath_len_wo_slash;
+ } else if (req->norm_indexes[0] == 0 && confpath_len_wo_slash == 0) {
+ /* path without leading slash */
+ path_info_start = 0;
+ } else {
+ path_info_start = req->norm_indexes[confpath_len_wo_slash] - 1;
+ }
+
+ return mrb_str_new(mrb, req->path.base + path_info_start, path_info_end - path_info_start);
+}
+
+static mrb_value build_env(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_shared_context_t *shared = generator->ctx->shared;
+ mrb_state *mrb = shared->mrb;
+ mrb_value env = mrb_hash_new_capa(mrb, 16);
+ char http_version[sizeof("HTTP/1.0")];
+ size_t http_version_sz;
+
+ /* environment */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_REQUEST_METHOD),
+ mrb_str_new(mrb, generator->req->method.base, generator->req->method.len));
+
+ size_t confpath_len_wo_slash = generator->req->pathconf->path.len;
+ if (generator->req->pathconf->path.base[generator->req->pathconf->path.len - 1] == '/')
+ --confpath_len_wo_slash;
+ assert(confpath_len_wo_slash <= generator->req->path_normalized.len);
+
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SCRIPT_NAME),
+ mrb_str_new(mrb, generator->req->pathconf->path.base, confpath_len_wo_slash));
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_PATH_INFO), build_path_info(mrb, generator->req, confpath_len_wo_slash));
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_QUERY_STRING),
+ generator->req->query_at != SIZE_MAX ? mrb_str_new(mrb, generator->req->path.base + generator->req->query_at + 1,
+ generator->req->path.len - (generator->req->query_at + 1))
+ : mrb_str_new_lit(mrb, ""));
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_NAME),
+ mrb_str_new(mrb, generator->req->hostconf->authority.host.base, generator->req->hostconf->authority.host.len));
+ http_version_sz = h2o_stringify_protocol_version(http_version, generator->req->version);
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_PROTOCOL),
+ mrb_str_new(mrb, http_version, http_version_sz));
+ {
+ mrb_value h, p;
+ stringify_address(generator->req->conn, generator->req->conn->callbacks->get_sockname, mrb, &h, &p);
+ if (!mrb_nil_p(h))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_ADDR), h);
+ if (!mrb_nil_p(p))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_PORT), p);
+ }
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_TOKEN_HOST - h2o__tokens),
+ mrb_str_new(mrb, generator->req->authority.base, generator->req->authority.len));
+ if (generator->req->entity.base != NULL) {
+ char buf[32];
+ int l = sprintf(buf, "%zu", generator->req->entity.len);
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_CONTENT_LENGTH), mrb_str_new(mrb, buf, l));
+ generator->rack_input = mrb_input_stream_value(mrb, NULL, 0);
+ mrb_input_stream_set_data(mrb, generator->rack_input, generator->req->entity.base, (mrb_int)generator->req->entity.len, 0,
+ on_rack_input_free, &generator->rack_input);
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_INPUT), generator->rack_input);
+ }
+ {
+ mrb_value h, p;
+ stringify_address(generator->req->conn, generator->req->conn->callbacks->get_peername, mrb, &h, &p);
+ if (!mrb_nil_p(h))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_REMOTE_ADDR), h);
+ if (!mrb_nil_p(p))
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_REMOTE_PORT), p);
+ }
+ {
+ size_t i;
+ for (i = 0; i != generator->req->env.size; i += 2) {
+ h2o_iovec_t *name = generator->req->env.entries + i, *value = name + 1;
+ mrb_hash_set(mrb, env, mrb_str_new(mrb, name->base, name->len), mrb_str_new(mrb, value->base, value->len));
+ }
+ }
+
+ { /* headers */
+ h2o_header_t **headers_sorted = alloca(sizeof(*headers_sorted) * generator->req->headers.size);
+ size_t i;
+ for (i = 0; i != generator->req->headers.size; ++i)
+ headers_sorted[i] = generator->req->headers.entries + i;
+ qsort(headers_sorted, generator->req->headers.size, sizeof(*headers_sorted), build_env_sort_header_cb);
+ for (i = 0; i != generator->req->headers.size; ++i) {
+ const h2o_header_t *header = headers_sorted[i];
+ mrb_value n, v;
+ if (h2o_iovec_is_token(header->name)) {
+ const h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, header->name);
+ if (token == H2O_TOKEN_TRANSFER_ENCODING)
+ continue;
+ n = mrb_ary_entry(shared->constants, (mrb_int)(token - h2o__tokens));
+ } else {
+ h2o_iovec_t vec = convert_header_name_to_env(&generator->req->pool, header->name->base, header->name->len);
+ n = mrb_str_new(mrb, vec.base, vec.len);
+ }
+ v = mrb_str_new(mrb, header->value.base, header->value.len);
+ while (i < generator->req->headers.size - 1) {
+ if (!h2o_memis(headers_sorted[i + 1]->name->base, headers_sorted[i + 1]->name->len, header->name->base,
+ header->name->len))
+ break;
+ header = headers_sorted[++i];
+ v = mrb_str_append(mrb, v, mrb_ary_entry(shared->constants,
+ header->name == &H2O_TOKEN_COOKIE->buf ? H2O_MRUBY_LIT_SEPARATOR_SEMICOLON
+ : H2O_MRUBY_LIT_SEPARATOR_COMMA));
+ v = mrb_str_append(mrb, v, mrb_str_new(mrb, header->value.base, header->value.len));
+ }
+ mrb_hash_set(mrb, env, n, v);
+ }
+ }
+
+ /* rack.* */
+ /* TBD rack.version? */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_URL_SCHEME),
+ mrb_str_new(mrb, generator->req->scheme->name.base, generator->req->scheme->name.len));
+ /* we are using shared-none architecture, and therefore declare ourselves as multiprocess */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_MULTITHREAD), mrb_false_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_MULTIPROCESS), mrb_true_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_RUN_ONCE), mrb_false_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_HIJACK_), mrb_false_value());
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_RACK_ERRORS),
+ mrb_gv_get(mrb, mrb_intern_lit(mrb, "$stderr")));
+
+ /* server name */
+ mrb_hash_set(mrb, env, mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_SOFTWARE),
+ mrb_ary_entry(shared->constants, H2O_MRUBY_LIT_SERVER_SOFTWARE_VALUE));
+
+ return env;
+}
+
+static int handle_response_header(h2o_mruby_context_t *handler_ctx, h2o_iovec_t name, h2o_iovec_t value, void *_req)
+{
+ h2o_req_t *req = _req;
+ const h2o_token_t *token;
+ static const h2o_iovec_t fallthru_set_prefix = {H2O_STRLIT(FALLTHRU_SET_PREFIX)};
+
+ /* convert name to lowercase */
+ name = h2o_strdup(&req->pool, name.base, name.len);
+ h2o_strtolower(name.base, name.len);
+
+ if ((token = h2o_lookup_token(name.base, name.len)) != NULL) {
+ if (token->proxy_should_drop_for_res) {
+ /* skip */
+ } else if (token == H2O_TOKEN_CONTENT_LENGTH) {
+ req->res.content_length = h2o_strtosize(value.base, value.len);
+ } else {
+ value = h2o_strdup(&req->pool, value.base, value.len);
+ if (token == H2O_TOKEN_LINK) {
+ h2o_iovec_t new_value = h2o_push_path_in_link_header(req, value.base, value.len);
+ if (new_value.len)
+ h2o_add_header(&req->pool, &req->res.headers, token, NULL, new_value.base, new_value.len);
+ } else {
+ h2o_add_header(&req->pool, &req->res.headers, token, NULL, value.base, value.len);
+ }
+ }
+ } else if (name.len > fallthru_set_prefix.len &&
+ h2o_memis(name.base, fallthru_set_prefix.len, fallthru_set_prefix.base, fallthru_set_prefix.len)) {
+ /* register environment variables (with the name converted to uppercase, and using `_`) */
+ size_t i;
+ name.base += fallthru_set_prefix.len;
+ name.len -= fallthru_set_prefix.len;
+ for (i = 0; i != name.len; ++i)
+ name.base[i] = name.base[i] == '-' ? '_' : h2o_toupper(name.base[i]);
+ h2o_iovec_t *slot = h2o_req_getenv(req, name.base, name.len, 1);
+ *slot = h2o_strdup(&req->pool, value.base, value.len);
+ } else {
+ value = h2o_strdup(&req->pool, value.base, value.len);
+ h2o_add_header_by_str(&req->pool, &req->res.headers, name.base, name.len, 0, NULL, value.base, value.len);
+ }
+
+ return 0;
+}
+
+static void clear_rack_input(h2o_mruby_generator_t *generator)
+{
+ if (!mrb_nil_p(generator->rack_input))
+ mrb_input_stream_set_data(generator->ctx->shared->mrb, generator->rack_input, NULL, -1, 0, NULL, NULL);
+}
+
+static void on_generator_dispose(void *_generator)
+{
+ h2o_mruby_generator_t *generator = _generator;
+
+ clear_rack_input(generator);
+ generator->req = NULL;
+
+ if (generator->chunked != NULL)
+ h2o_mruby_send_chunked_dispose(generator);
+}
+
+static int on_req(h2o_handler_t *_handler, h2o_req_t *req)
+{
+ h2o_mruby_handler_t *handler = (void *)_handler;
+ h2o_mruby_shared_context_t *shared = get_shared_context(req->conn->ctx);
+ int gc_arena = mrb_gc_arena_save(shared->mrb);
+
+ h2o_mruby_generator_t *generator = h2o_mem_alloc_shared(&req->pool, sizeof(*generator), on_generator_dispose);
+ generator->super.proceed = NULL;
+ generator->super.stop = NULL;
+ generator->req = req;
+ generator->ctx = h2o_context_get_handler_context(req->conn->ctx, &handler->super);
+ generator->rack_input = mrb_nil_value();
+ generator->chunked = NULL;
+
+ mrb_value env = build_env(generator);
+
+ int is_delegate = 0;
+ h2o_mruby_run_fiber(generator, generator->ctx->proc, env, &is_delegate);
+
+ mrb_gc_arena_restore(shared->mrb, gc_arena);
+ if (is_delegate)
+ return -1;
+ return 0;
+}
+
+static void send_response(h2o_mruby_generator_t *generator, mrb_int status, mrb_value resp, int *is_delegate)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ mrb_value body;
+ h2o_iovec_t content = {NULL};
+
+ /* set status */
+ generator->req->res.status = (int)status;
+
+ /* set headers */
+ if (h2o_mruby_iterate_headers(generator->ctx, mrb_ary_entry(resp, 1), handle_response_header, generator->req) != 0) {
+ assert(mrb->exc != NULL);
+ goto GotException;
+ }
+
+ /* return without processing body, if status is fallthru */
+ if (generator->req->res.status == STATUS_FALLTHRU) {
+ if (is_delegate != NULL)
+ *is_delegate = 1;
+ else
+ h2o_delegate_request_deferred(generator->req, &generator->ctx->handler->super);
+ return;
+ }
+
+ /* obtain body */
+ body = mrb_ary_entry(resp, 2);
+
+ /* flatten body if possible */
+ if (mrb_array_p(body)) {
+ mrb_int i, len = RARRAY_LEN(body);
+ /* calculate the length of the output, while at the same time converting the elements of the output array to string */
+ content.len = 0;
+ for (i = 0; i != len; ++i) {
+ mrb_value e = mrb_ary_entry(body, i);
+ if (!mrb_string_p(e)) {
+ e = h2o_mruby_to_str(mrb, e);
+ if (mrb->exc != NULL)
+ goto GotException;
+ mrb_ary_set(mrb, body, i, e);
+ }
+ content.len += RSTRING_LEN(e);
+ }
+ /* allocate memory, and copy the response */
+ char *dst = content.base = h2o_mem_alloc_pool(&generator->req->pool, content.len);
+ for (i = 0; i != len; ++i) {
+ mrb_value e = mrb_ary_entry(body, i);
+ assert(mrb_string_p(e));
+ memcpy(dst, RSTRING_PTR(e), RSTRING_LEN(e));
+ dst += RSTRING_LEN(e);
+ }
+ /* reset body to nil, now that we have read all data */
+ body = mrb_nil_value();
+ }
+
+ /* use fiber in case we need to call #each */
+ if (!mrb_nil_p(body)) {
+ h2o_start_response(generator->req, &generator->super);
+ mrb_value receiver = h2o_mruby_send_chunked_init(generator, body);
+ if (!mrb_nil_p(receiver))
+ h2o_mruby_run_fiber(generator, receiver, body, 0);
+ return;
+ }
+
+ /* send the entire response immediately */
+ if (status == 101 || status == 204 || status == 304 ||
+ h2o_memis(generator->req->input.method.base, generator->req->input.method.len, H2O_STRLIT("HEAD"))) {
+ h2o_start_response(generator->req, &generator->super);
+ h2o_send(generator->req, NULL, 0, H2O_SEND_STATE_FINAL);
+ } else {
+ if (content.len < generator->req->res.content_length) {
+ generator->req->res.content_length = content.len;
+ } else {
+ content.len = generator->req->res.content_length;
+ }
+ h2o_start_response(generator->req, &generator->super);
+ h2o_send(generator->req, &content, 1, H2O_SEND_STATE_FINAL);
+ }
+ return;
+
+GotException:
+ report_exception(generator->req, mrb);
+ h2o_send_error_500(generator->req, "Internal Server Error", "Internal Server Error", 0);
+}
+
+void h2o_mruby_run_fiber(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value input, int *is_delegate)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ mrb_value output;
+ mrb_int status;
+
+ if (!mrb_obj_eq(mrb, generator->ctx->proc, receiver)) {
+ mrb_gc_unregister(mrb, receiver);
+ mrb_gc_protect(mrb, receiver);
+ }
+
+ h2o_mruby_current_generator = generator;
+
+ while (1) {
+ /* send input to fiber */
+ output = mrb_funcall_argv(mrb, receiver, generator->ctx->shared->symbols.sym_call, 1, &input);
+ if (mrb->exc != NULL)
+ goto GotException;
+ if (!mrb_array_p(output)) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "rack app did not return an array"));
+ goto GotException;
+ }
+ /* fetch status */
+ mrb_value v = mrb_to_int(mrb, mrb_ary_entry(output, 0));
+ if (mrb->exc != NULL)
+ goto GotException;
+ status = mrb_fixnum(v);
+ /* take special action depending on the status code */
+ if (status < 0) {
+ if (status == H2O_MRUBY_CALLBACK_ID_EXCEPTION_RAISED) {
+ mrb->exc = mrb_obj_ptr(mrb_ary_entry(output, 1));
+ goto GotException;
+ }
+ receiver = mrb_ary_entry(output, 1);
+ int next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_IMMEDIATE;
+ mrb_value args = mrb_ary_entry(output, 2);
+ if (mrb_array_p(args)) {
+ switch (status) {
+ case H2O_MRUBY_CALLBACK_ID_SEND_CHUNKED_EOS:
+ input = h2o_mruby_send_chunked_eos_callback(generator, receiver, args, &next_action);
+ break;
+ case H2O_MRUBY_CALLBACK_ID_HTTP_JOIN_RESPONSE:
+ input = h2o_mruby_http_join_response_callback(generator, receiver, args, &next_action);
+ break;
+ case H2O_MRUBY_CALLBACK_ID_HTTP_FETCH_CHUNK:
+ input = h2o_mruby_http_fetch_chunk_callback(generator, receiver, args, &next_action);
+ break;
+ default:
+ input = mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "unexpected callback id sent from rack app");
+ break;
+ }
+ } else {
+ input = mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "callback from rack app did not receive an array arg");
+ }
+ switch (next_action) {
+ case H2O_MRUBY_CALLBACK_NEXT_ACTION_STOP:
+ return;
+ case H2O_MRUBY_CALLBACK_NEXT_ACTION_ASYNC:
+ goto Async;
+ default:
+ assert(next_action == H2O_MRUBY_CALLBACK_NEXT_ACTION_IMMEDIATE);
+ break;
+ }
+ goto Next;
+ }
+ /* if no special actions were necessary, then the output is a rack response */
+ break;
+ Next:
+ mrb_gc_protect(mrb, receiver);
+ mrb_gc_protect(mrb, input);
+ }
+
+ h2o_mruby_current_generator = NULL;
+
+ if (!(100 <= status && status <= 999)) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "status returned from rack app is out of range"));
+ goto GotException;
+ }
+
+ /* send the response (unless req is already closed) */
+ if (generator->req == NULL)
+ return;
+ if (generator->req->_generator != NULL) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "unexpectedly received a rack response"));
+ goto GotException;
+ }
+ send_response(generator, status, output, is_delegate);
+ return;
+
+GotException:
+ h2o_mruby_current_generator = NULL;
+ if (generator->req != NULL) {
+ report_exception(generator->req, mrb);
+ if (generator->req->_generator == NULL) {
+ h2o_send_error_500(generator->req, "Internal Server Error", "Internal Server Error", 0);
+ } else {
+ h2o_mruby_send_chunked_close(generator);
+ }
+ }
+ return;
+
+Async:
+ h2o_mruby_current_generator = NULL;
+ if (!mrb_obj_eq(mrb, generator->ctx->proc, receiver))
+ mrb_gc_register(mrb, receiver);
+ return;
+}
+
+h2o_mruby_handler_t *h2o_mruby_register(h2o_pathconf_t *pathconf, h2o_mruby_config_vars_t *vars)
+{
+ h2o_mruby_handler_t *handler = (void *)h2o_create_handler(pathconf, sizeof(*handler));
+
+ handler->super.on_context_init = on_context_init;
+ handler->super.on_context_dispose = on_context_dispose;
+ handler->super.dispose = on_handler_dispose;
+ handler->super.on_req = on_req;
+ handler->config.source = h2o_strdup(NULL, vars->source.base, vars->source.len);
+ if (vars->path != NULL)
+ handler->config.path = h2o_strdup(NULL, vars->path, SIZE_MAX).base;
+ handler->config.lineno = vars->lineno;
+
+ return handler;
+}
+
+mrb_value h2o_mruby_each_to_array(h2o_mruby_context_t *handler_ctx, mrb_value src)
+{
+ return mrb_funcall_argv(handler_ctx->shared->mrb, mrb_ary_entry(handler_ctx->shared->constants, H2O_MRUBY_PROC_EACH_TO_ARRAY),
+ handler_ctx->shared->symbols.sym_call, 1, &src);
+}
+
+static int iterate_headers_handle_pair(h2o_mruby_context_t *handler_ctx, mrb_value name, mrb_value value,
+ int (*cb)(h2o_mruby_context_t *, h2o_iovec_t, h2o_iovec_t, void *), void *cb_data)
+{
+ mrb_state *mrb = handler_ctx->shared->mrb;
+
+ /* convert name and value to string */
+ name = h2o_mruby_to_str(mrb, name);
+ if (mrb->exc != NULL)
+ return -1;
+ value = h2o_mruby_to_str(mrb, value);
+ if (mrb->exc != NULL)
+ return -1;
+
+ /* call the callback, splitting the values with '\n' */
+ const char *vstart = RSTRING_PTR(value), *vend = vstart + RSTRING_LEN(value), *eol;
+ while (1) {
+ for (eol = vstart; eol != vend; ++eol)
+ if (*eol == '\n')
+ break;
+ if (cb(handler_ctx, h2o_iovec_init(RSTRING_PTR(name), RSTRING_LEN(name)), h2o_iovec_init(vstart, eol - vstart), cb_data) !=
+ 0)
+ return -1;
+ if (eol == vend)
+ break;
+ vstart = eol + 1;
+ }
+
+ return 0;
+}
+
+int h2o_mruby_iterate_headers(h2o_mruby_context_t *handler_ctx, mrb_value headers,
+ int (*cb)(h2o_mruby_context_t *, h2o_iovec_t, h2o_iovec_t, void *), void *cb_data)
+{
+ mrb_state *mrb = handler_ctx->shared->mrb;
+
+ if (!(mrb_hash_p(headers) || mrb_array_p(headers))) {
+ headers = h2o_mruby_each_to_array(handler_ctx, headers);
+ if (mrb->exc != NULL)
+ return -1;
+ assert(mrb_array_p(headers));
+ }
+
+ if (mrb_hash_p(headers)) {
+ mrb_value keys = mrb_hash_keys(mrb, headers);
+ mrb_int i, len = RARRAY_LEN(keys);
+ for (i = 0; i != len; ++i) {
+ mrb_value k = mrb_ary_entry(keys, i);
+ mrb_value v = mrb_hash_get(mrb, headers, k);
+ if (iterate_headers_handle_pair(handler_ctx, k, v, cb, cb_data) != 0)
+ return -1;
+ }
+ } else {
+ assert(mrb_array_p(headers));
+ mrb_int i, len = RARRAY_LEN(headers);
+ for (i = 0; i != len; ++i) {
+ mrb_value pair = mrb_ary_entry(headers, i);
+ if (!mrb_array_p(pair)) {
+ mrb->exc = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "array element of headers MUST by an array"));
+ return -1;
+ }
+ if (iterate_headers_handle_pair(handler_ctx, mrb_ary_entry(pair, 0), mrb_ary_entry(pair, 1), cb, cb_data) != 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/debian/vendor-h2o/lib/handler/mruby/chunked.c b/debian/vendor-h2o/lib/handler/mruby/chunked.c
new file mode 100644
index 0000000..28e3ae4
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/chunked.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <mruby.h>
+#include <mruby/array.h>
+#include <mruby/error.h>
+#include <mruby/string.h>
+#include "h2o/mruby_.h"
+#include "embedded.c.h"
+
+struct st_h2o_mruby_chunked_t {
+ h2o_doublebuffer_t sending;
+ size_t bytes_left; /* SIZE_MAX indicates that the number is undermined */
+ enum { H2O_MRUBY_CHUNKED_TYPE_CALLBACK, H2O_MRUBY_CHUNKED_TYPE_SHORTCUT } type;
+ union {
+ struct {
+ h2o_buffer_t *receiving;
+ mrb_value body_obj; /* becomes nil on eos */
+ } callback;
+ struct {
+ h2o_mruby_http_request_context_t *client;
+ h2o_buffer_t *remaining;
+ } shortcut;
+ };
+};
+
+static void do_send(h2o_mruby_generator_t *generator, h2o_buffer_t **input, int is_final)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+
+ assert(chunked->sending.bytes_inflight == 0);
+
+ h2o_iovec_t buf = h2o_doublebuffer_prepare(&chunked->sending, input, generator->req->preferred_chunk_size);
+ size_t bufcnt = 1;
+
+ if (is_final && buf.len == chunked->sending.buf->size && (*input)->size == 0) {
+ if (buf.len == 0)
+ --bufcnt;
+ /* terminate the H1 connection if the length of content served did not match the value sent in content-length header */
+ if (chunked->bytes_left != SIZE_MAX && chunked->bytes_left != 0)
+ generator->req->http1_is_persistent = 0;
+ } else {
+ if (buf.len == 0)
+ return;
+ is_final = 0;
+ }
+
+ h2o_send(generator->req, &buf, bufcnt, is_final ? H2O_SEND_STATE_FINAL : H2O_SEND_STATE_IN_PROGRESS);
+}
+
+static void do_proceed(h2o_generator_t *_generator, h2o_req_t *req)
+{
+ h2o_mruby_generator_t *generator = (void *)_generator;
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ h2o_buffer_t **input;
+ int is_final;
+
+ h2o_doublebuffer_consume(&chunked->sending);
+
+ switch (chunked->type) {
+ case H2O_MRUBY_CHUNKED_TYPE_CALLBACK:
+ input = &chunked->callback.receiving;
+ is_final = mrb_nil_p(chunked->callback.body_obj);
+ break;
+ case H2O_MRUBY_CHUNKED_TYPE_SHORTCUT:
+ if (chunked->shortcut.client != NULL) {
+ input = h2o_mruby_http_peek_content(chunked->shortcut.client, &is_final);
+ assert(!is_final);
+ } else {
+ input = &chunked->shortcut.remaining;
+ is_final = 1;
+ }
+ break;
+ default:
+ h2o_fatal("unexpected type");
+ break;
+ }
+
+ do_send(generator, input, is_final);
+}
+
+static void on_shortcut_notify(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ int is_final;
+ h2o_buffer_t **input = h2o_mruby_http_peek_content(chunked->shortcut.client, &is_final);
+
+ if (chunked->bytes_left != SIZE_MAX) {
+ if (chunked->bytes_left < (*input)->size)
+ (*input)->size = chunked->bytes_left; /* trim data too long */
+ chunked->bytes_left -= (*input)->size;
+ }
+
+ /* if final, steal socket input buffer to shortcut.remaining, and reset pointer to client */
+ if (is_final) {
+ chunked->shortcut.remaining = *input;
+ h2o_buffer_init(input, &h2o_socket_buffer_prototype);
+ input = &chunked->shortcut.remaining;
+ chunked->shortcut.client = NULL;
+ }
+
+ if (chunked->sending.bytes_inflight == 0)
+ do_send(generator, input, is_final);
+}
+
+static void close_body_obj(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ mrb_state *mrb = generator->ctx->shared->mrb;
+
+ if (!mrb_nil_p(chunked->callback.body_obj)) {
+ /* call close and throw away error */
+ if (mrb_respond_to(mrb, chunked->callback.body_obj, generator->ctx->shared->symbols.sym_close))
+ mrb_funcall_argv(mrb, chunked->callback.body_obj, generator->ctx->shared->symbols.sym_close, 0, NULL);
+ mrb->exc = NULL;
+ mrb_gc_unregister(mrb, chunked->callback.body_obj);
+ chunked->callback.body_obj = mrb_nil_value();
+ }
+}
+
+mrb_value h2o_mruby_send_chunked_init(h2o_mruby_generator_t *generator, mrb_value body)
+{
+ h2o_mruby_chunked_t *chunked = h2o_mem_alloc_pool(&generator->req->pool, sizeof(*chunked));
+ h2o_doublebuffer_init(&chunked->sending, &h2o_socket_buffer_prototype);
+ chunked->bytes_left = h2o_memis(generator->req->method.base, generator->req->method.len, H2O_STRLIT("HEAD"))
+ ? 0
+ : generator->req->res.content_length;
+ generator->super.proceed = do_proceed;
+ generator->chunked = chunked;
+
+ if ((chunked->shortcut.client = h2o_mruby_http_set_shortcut(generator->ctx->shared->mrb, body, on_shortcut_notify)) != NULL) {
+ chunked->type = H2O_MRUBY_CHUNKED_TYPE_SHORTCUT;
+ chunked->shortcut.remaining = NULL;
+ on_shortcut_notify(generator);
+ return mrb_nil_value();
+ } else {
+ chunked->type = H2O_MRUBY_CHUNKED_TYPE_CALLBACK;
+ h2o_buffer_init(&chunked->callback.receiving, &h2o_socket_buffer_prototype);
+ mrb_gc_register(generator->ctx->shared->mrb, body);
+ chunked->callback.body_obj = body;
+ return mrb_ary_entry(generator->ctx->shared->constants, H2O_MRUBY_CHUNKED_PROC_EACH_TO_FIBER);
+ }
+}
+
+void h2o_mruby_send_chunked_dispose(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+
+ h2o_doublebuffer_dispose(&chunked->sending);
+
+ switch (chunked->type) {
+ case H2O_MRUBY_CHUNKED_TYPE_CALLBACK:
+ h2o_buffer_dispose(&chunked->callback.receiving);
+ close_body_obj(generator);
+ break;
+ case H2O_MRUBY_CHUNKED_TYPE_SHORTCUT:
+ /* note: no need to free reference from chunked->client, since it is disposed at the same moment */
+ if (chunked->shortcut.remaining != NULL)
+ h2o_buffer_dispose(&chunked->shortcut.remaining);
+ break;
+ }
+}
+
+static mrb_value check_precond(mrb_state *mrb, h2o_mruby_generator_t *generator)
+{
+ if (generator == NULL || generator->req == NULL)
+ return mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "downstream HTTP closed");
+ if (generator->req->_generator == NULL)
+ return mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "cannot send chunk before sending headers");
+ return mrb_nil_value();
+}
+
+static mrb_value send_chunked_method(mrb_state *mrb, mrb_value self)
+{
+ h2o_mruby_generator_t *generator = h2o_mruby_current_generator;
+ const char *s;
+ mrb_int len;
+
+ /* parse args */
+ mrb_get_args(mrb, "s", &s, &len);
+
+ { /* precond check */
+ mrb_value exc = check_precond(mrb, generator);
+ if (!mrb_nil_p(exc))
+ mrb_exc_raise(mrb, exc);
+ }
+
+ /* append to send buffer, and send out immediately if necessary */
+ if (len != 0) {
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+ if (chunked->bytes_left != SIZE_MAX) {
+ if (len > chunked->bytes_left)
+ len = chunked->bytes_left;
+ chunked->bytes_left -= len;
+ }
+ if (len != 0) {
+ h2o_buffer_reserve(&chunked->callback.receiving, len);
+ memcpy(chunked->callback.receiving->bytes + chunked->callback.receiving->size, s, len);
+ chunked->callback.receiving->size += len;
+ if (chunked->sending.bytes_inflight == 0)
+ do_send(generator, &chunked->callback.receiving, 0);
+ }
+ }
+
+ return mrb_nil_value();
+}
+
+mrb_value h2o_mruby_send_chunked_eos_callback(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value input,
+ int *next_action)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+
+ { /* precond check */
+ mrb_value exc = check_precond(mrb, generator);
+ if (!mrb_nil_p(exc))
+ return exc;
+ }
+
+ h2o_mruby_send_chunked_close(generator);
+
+ *next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_STOP;
+ return mrb_nil_value();
+}
+
+void h2o_mruby_send_chunked_close(h2o_mruby_generator_t *generator)
+{
+ h2o_mruby_chunked_t *chunked = generator->chunked;
+
+ /* run_fiber will never be called once we enter the fast path, and therefore this function will never get called in that case */
+ assert(chunked->type == H2O_MRUBY_CHUNKED_TYPE_CALLBACK);
+
+ close_body_obj(generator);
+
+ if (chunked->sending.bytes_inflight == 0)
+ do_send(generator, &chunked->callback.receiving, 1);
+}
+
+void h2o_mruby_send_chunked_init_context(h2o_mruby_shared_context_t *shared_ctx)
+{
+ mrb_state *mrb = shared_ctx->mrb;
+
+ h2o_mruby_eval_expr(mrb, H2O_MRUBY_CODE_CHUNKED);
+ h2o_mruby_assert(mrb);
+
+ mrb_define_method(mrb, mrb->kernel_module, "_h2o_send_chunk", send_chunked_method, MRB_ARGS_ARG(1, 0));
+ h2o_mruby_define_callback(mrb, "_h2o_send_chunk_eos", H2O_MRUBY_CALLBACK_ID_SEND_CHUNKED_EOS);
+
+ mrb_ary_set(mrb, shared_ctx->constants, H2O_MRUBY_CHUNKED_PROC_EACH_TO_FIBER,
+ mrb_funcall(mrb, mrb_top_self(mrb), "_h2o_chunked_proc_each_to_fiber", 0));
+ h2o_mruby_assert(mrb);
+}
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded.c.h b/debian/vendor-h2o/lib/handler/mruby/embedded.c.h
new file mode 100644
index 0000000..db4bb23
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded.c.h
@@ -0,0 +1,111 @@
+/*
+ * DO NOT EDIT! generated by embed_mruby_code.pl
+ * Please refer to the respective source files for copyright information.
+ */
+
+/* lib/handler/mruby/embedded/core.rb */
+#define H2O_MRUBY_CODE_CORE \
+ "module Kernel\n" \
+ " def _h2o_define_callback(name, id)\n" \
+ " Kernel.define_method(name) do |*args|\n" \
+ " ret = Fiber.yield([ id, _h2o_create_resumer(), args ])\n" \
+ " if ret.kind_of? Exception\n" \
+ " raise ret\n" \
+ " end\n" \
+ " ret\n" \
+ " end\n" \
+ " end\n" \
+ " def _h2o_create_resumer()\n" \
+ " me = Fiber.current\n" \
+ " Proc.new do |v|\n" \
+ " me.resume(v)\n" \
+ " end\n" \
+ " end\n" \
+ " def _h2o_proc_each_to_array()\n" \
+ " Proc.new do |o|\n" \
+ " a = []\n" \
+ " o.each do |x|\n" \
+ " a << x\n" \
+ " end\n" \
+ " a\n" \
+ " end\n" \
+ " end\n" \
+ " def _h2o_proc_app_to_fiber()\n" \
+ " Proc.new do |app|\n" \
+ " cached = nil\n" \
+ " Proc.new do |req|\n" \
+ " fiber = cached\n" \
+ " cached = nil\n" \
+ " if !fiber\n" \
+ " fiber = Fiber.new do\n" \
+ " self_fiber = Fiber.current\n" \
+ " req = Fiber.yield\n" \
+ " while 1\n" \
+ " begin\n" \
+ " while 1\n" \
+ " resp = app.call(req)\n" \
+ " cached = self_fiber\n" \
+ " req = Fiber.yield(resp)\n" \
+ " end\n" \
+ " rescue => e\n" \
+ " cached = self_fiber\n" \
+ " req = Fiber.yield([-1, e])\n" \
+ " end\n" \
+ " end\n" \
+ " end\n" \
+ " fiber.resume\n" \
+ " end\n" \
+ " fiber.resume(req)\n" \
+ " end\n" \
+ " end\n" \
+ " end\n" \
+ "end\n"
+
+/* lib/handler/mruby/embedded/http_request.rb */
+#define H2O_MRUBY_CODE_HTTP_REQUEST \
+ "module H2O\n" \
+ " class HttpRequest\n" \
+ " def join\n" \
+ " if !@resp\n" \
+ " @resp = _h2o__http_join_response(self)\n" \
+ " end\n" \
+ " @resp\n" \
+ " end\n" \
+ " def _set_response(resp)\n" \
+ " @resp = resp\n" \
+ " end\n" \
+ " end\n" \
+ " class HttpInputStream\n" \
+ " def each\n" \
+ " while c = _h2o__http_fetch_chunk(self)\n" \
+ " yield c\n" \
+ " end\n" \
+ " end\n" \
+ " def join\n" \
+ " s = \"\"\n" \
+ " each do |c|\n" \
+ " s << c\n" \
+ " end\n" \
+ " s\n" \
+ " end\n" \
+ " class Empty < HttpInputStream\n" \
+ " def each; end\n" \
+ " end\n" \
+ " end\n" \
+ "end\n"
+
+/* lib/handler/mruby/embedded/chunked.rb */
+#define H2O_MRUBY_CODE_CHUNKED \
+ "module Kernel\n" \
+ " def _h2o_chunked_proc_each_to_fiber()\n" \
+ " Proc.new do |src|\n" \
+ " fiber = Fiber.new do\n" \
+ " src.each do |chunk|\n" \
+ " _h2o_send_chunk(chunk)\n" \
+ " end\n" \
+ " _h2o_send_chunk_eos()\n" \
+ " end\n" \
+ " fiber.resume\n" \
+ " end\n" \
+ " end\n" \
+ "end\n"
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb b/debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb
new file mode 100644
index 0000000..ff4e578
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded/chunked.rb
@@ -0,0 +1,35 @@
+# Copyright (c) 2014 DeNA Co., Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+module Kernel
+
+ def _h2o_chunked_proc_each_to_fiber()
+ Proc.new do |src|
+ fiber = Fiber.new do
+ src.each do |chunk|
+ _h2o_send_chunk(chunk)
+ end
+ _h2o_send_chunk_eos()
+ end
+ fiber.resume
+ end
+ end
+
+end
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded/core.rb b/debian/vendor-h2o/lib/handler/mruby/embedded/core.rb
new file mode 100644
index 0000000..e62583d
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded/core.rb
@@ -0,0 +1,81 @@
+# Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Ryosuke Matsumoto,
+# Masayoshi Takahashi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+module Kernel
+
+ def _h2o_define_callback(name, id)
+ Kernel.define_method(name) do |*args|
+ ret = Fiber.yield([ id, _h2o_create_resumer(), args ])
+ if ret.kind_of? Exception
+ raise ret
+ end
+ ret
+ end
+ end
+
+ def _h2o_create_resumer()
+ me = Fiber.current
+ Proc.new do |v|
+ me.resume(v)
+ end
+ end
+
+ def _h2o_proc_each_to_array()
+ Proc.new do |o|
+ a = []
+ o.each do |x|
+ a << x
+ end
+ a
+ end
+ end
+
+ def _h2o_proc_app_to_fiber()
+ Proc.new do |app|
+ cached = nil
+ Proc.new do |req|
+ fiber = cached
+ cached = nil
+ if !fiber
+ fiber = Fiber.new do
+ self_fiber = Fiber.current
+ req = Fiber.yield
+ while 1
+ begin
+ while 1
+ resp = app.call(req)
+ cached = self_fiber
+ req = Fiber.yield(resp)
+ end
+ rescue => e
+ cached = self_fiber
+ req = Fiber.yield([-1, e])
+ end
+ end
+ end
+ fiber.resume
+ end
+ fiber.resume(req)
+ end
+ end
+ end
+
+end
diff --git a/debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb b/debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb
new file mode 100644
index 0000000..3f3247a
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/embedded/http_request.rb
@@ -0,0 +1,54 @@
+# Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+module H2O
+
+ class HttpRequest
+ def join
+ if !@resp
+ @resp = _h2o__http_join_response(self)
+ end
+ @resp
+ end
+ def _set_response(resp)
+ @resp = resp
+ end
+ end
+
+ class HttpInputStream
+ def each
+ while c = _h2o__http_fetch_chunk(self)
+ yield c
+ end
+ end
+ def join
+ s = ""
+ each do |c|
+ s << c
+ end
+ s
+ end
+
+ class Empty < HttpInputStream
+ def each; end
+ end
+ end
+
+end
diff --git a/debian/vendor-h2o/lib/handler/mruby/http_request.c b/debian/vendor-h2o/lib/handler/mruby/http_request.c
new file mode 100644
index 0000000..964d03f
--- /dev/null
+++ b/debian/vendor-h2o/lib/handler/mruby/http_request.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2015-2016 DeNA Co., Ltd., Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <mruby.h>
+#include <mruby/array.h>
+#include <mruby/error.h>
+#include <mruby/hash.h>
+#include <mruby/string.h>
+#include <mruby_input_stream.h>
+#include "h2o/mruby_.h"
+#include "embedded.c.h"
+
+struct st_h2o_mruby_http_request_context_t {
+ h2o_mruby_generator_t *generator;
+ h2o_http1client_t *client;
+ mrb_value receiver;
+ struct {
+ h2o_buffer_t *buf;
+ h2o_iovec_t body; /* body.base != NULL indicates that post content exists (and the length MAY be zero) */
+ unsigned method_is_head : 1;
+ unsigned has_transfer_encoding : 1;
+ } req;
+ struct {
+ h2o_buffer_t *after_closed; /* when client becomes NULL, rest of the data will be stored to this pointer */
+ int has_content;
+ } resp;
+ struct {
+ mrb_value request;
+ mrb_value input_stream;
+ } refs;
+ void (*shortcut_notify_cb)(h2o_mruby_generator_t *generator);
+};
+
+static void on_gc_dispose_request(mrb_state *mrb, void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+ if (ctx != NULL)
+ ctx->refs.request = mrb_nil_value();
+}
+
+const static struct mrb_data_type request_type = {"http_request", on_gc_dispose_request};
+
+static void on_gc_dispose_input_stream(mrb_state *mrb, void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+ if (ctx != NULL)
+ ctx->refs.input_stream = mrb_nil_value();
+}
+
+const static struct mrb_data_type input_stream_type = {"http_input_stream", on_gc_dispose_input_stream};
+
+static mrb_value create_downstream_closed_exception(mrb_state *mrb)
+{
+ return mrb_exc_new_str_lit(mrb, E_RUNTIME_ERROR, "downstream HTTP closed");
+}
+
+static mrb_value detach_receiver(struct st_h2o_mruby_http_request_context_t *ctx)
+{
+ mrb_value ret = ctx->receiver;
+ assert(!mrb_nil_p(ret));
+ ctx->receiver = mrb_nil_value();
+ return ret;
+}
+
+static void on_dispose(void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+
+ /* clear the refs */
+ if (ctx->client != NULL) {
+ h2o_http1client_cancel(ctx->client);
+ ctx->client = NULL;
+ }
+ if (!mrb_nil_p(ctx->refs.request))
+ DATA_PTR(ctx->refs.request) = NULL;
+ if (!mrb_nil_p(ctx->refs.input_stream))
+ DATA_PTR(ctx->refs.input_stream) = NULL;
+
+ /* clear bufs */
+ h2o_buffer_dispose(&ctx->req.buf);
+ h2o_buffer_dispose(&ctx->resp.after_closed);
+
+ /* notify the app, if it is waiting to hear from us */
+ if (!mrb_nil_p(ctx->receiver)) {
+ mrb_state *mrb = ctx->generator->ctx->shared->mrb;
+ int gc_arena = mrb_gc_arena_save(mrb);
+ h2o_mruby_run_fiber(ctx->generator, detach_receiver(ctx), create_downstream_closed_exception(mrb), NULL);
+ mrb_gc_arena_restore(mrb, gc_arena);
+ }
+}
+
+static void post_response(struct st_h2o_mruby_http_request_context_t *ctx, int status, const h2o_header_t *headers_sorted,
+ size_t num_headers)
+{
+ mrb_state *mrb = ctx->generator->ctx->shared->mrb;
+ int gc_arena = mrb_gc_arena_save(mrb);
+ size_t i;
+
+ mrb_value resp = mrb_ary_new_capa(mrb, 3);
+
+ /* set status */
+ mrb_ary_set(mrb, resp, 0, mrb_fixnum_value(status));
+
+ /* set headers */
+ mrb_value headers_hash = mrb_hash_new_capa(mrb, (int)num_headers);
+ for (i = 0; i < num_headers; ++i) {
+ /* skip the headers, we determine the eos! */
+ if (h2o_memis(headers_sorted[i].name, headers_sorted[i].name->len, H2O_STRLIT("content-length")) ||
+ h2o_memis(headers_sorted[i].name, headers_sorted[i].name->len, H2O_STRLIT("transfer-encoding")))
+ continue;
+ /* build and set the hash entry */
+ mrb_value k = mrb_str_new(mrb, headers_sorted[i].name->base, headers_sorted[i].name->len);
+ mrb_value v = mrb_str_new(mrb, headers_sorted[i].value.base, headers_sorted[i].value.len);
+ while (i + 1 < num_headers && h2o_memis(headers_sorted[i].name->base, headers_sorted[i].name->len,
+ headers_sorted[i + 1].name->base, headers_sorted[i + 1].name->len)) {
+ ++i;
+ v = mrb_str_cat_lit(mrb, v, "\n");
+ v = mrb_str_cat(mrb, v, headers_sorted[i].value.base, headers_sorted[i].value.len);
+ }
+ mrb_hash_set(mrb, headers_hash, k, v);
+ }
+ mrb_ary_set(mrb, resp, 1, headers_hash);
+
+ /* set input stream */
+ assert(mrb_nil_p(ctx->refs.input_stream));
+ mrb_value input_stream_class;
+ if (ctx->req.method_is_head || status == 101 || status == 204 || status == 304) {
+ input_stream_class = mrb_ary_entry(ctx->generator->ctx->shared->constants, H2O_MRUBY_HTTP_EMPTY_INPUT_STREAM_CLASS);
+ } else {
+ input_stream_class = mrb_ary_entry(ctx->generator->ctx->shared->constants, H2O_MRUBY_HTTP_INPUT_STREAM_CLASS);
+ }
+ ctx->refs.input_stream = h2o_mruby_create_data_instance(mrb, input_stream_class, ctx, &input_stream_type);
+ mrb_ary_set(mrb, resp, 2, ctx->refs.input_stream);
+
+ if (mrb_nil_p(ctx->receiver)) {
+ /* is async */
+ mrb_funcall(mrb, ctx->refs.request, "_set_response", 1, resp);
+ if (mrb->exc != NULL) {
+ fprintf(stderr, "_set_response failed\n");
+ abort();
+ }
+ } else {
+ /* send response to the waiting receiver */
+ h2o_mruby_run_fiber(ctx->generator, detach_receiver(ctx), resp, NULL);
+ }
+
+ mrb_gc_arena_restore(mrb, gc_arena);
+}
+
+static void post_error(struct st_h2o_mruby_http_request_context_t *ctx, const char *errstr)
+{
+ static const h2o_header_t headers_sorted[] = {
+ {&H2O_TOKEN_CONTENT_TYPE->buf, NULL, {H2O_STRLIT("text/plain; charset=utf-8")}},
+ };
+
+ ctx->client = NULL;
+ size_t errstr_len = strlen(errstr);
+ h2o_buffer_reserve(&ctx->resp.after_closed, errstr_len);
+ memcpy(ctx->resp.after_closed->bytes + ctx->resp.after_closed->size, errstr, errstr_len);
+ ctx->resp.after_closed->size += errstr_len;
+ ctx->resp.has_content = 1;
+
+ post_response(ctx, 500, headers_sorted, sizeof(headers_sorted) / sizeof(headers_sorted[0]));
+}
+
+static mrb_value build_chunk(struct st_h2o_mruby_http_request_context_t *ctx)
+{
+ mrb_value chunk;
+
+ assert(ctx->resp.has_content);
+
+ if (ctx->client != NULL) {
+ assert(ctx->client->sock->input->size != 0);
+ chunk = mrb_str_new(ctx->generator->ctx->shared->mrb, ctx->client->sock->input->bytes, ctx->client->sock->input->size);
+ h2o_buffer_consume(&ctx->client->sock->input, ctx->client->sock->input->size);
+ ctx->resp.has_content = 0;
+ } else {
+ if (ctx->resp.after_closed->size == 0) {
+ chunk = mrb_nil_value();
+ } else {
+ chunk = mrb_str_new(ctx->generator->ctx->shared->mrb, ctx->resp.after_closed->bytes, ctx->resp.after_closed->size);
+ h2o_buffer_consume(&ctx->resp.after_closed, ctx->resp.after_closed->size);
+ }
+ /* has_content is retained as true, so that repeated calls will return nil immediately */
+ }
+
+ return chunk;
+}
+
+static int on_body(h2o_http1client_t *client, const char *errstr)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = client->data;
+
+ if (errstr != NULL) {
+ h2o_buffer_t *tmp = ctx->resp.after_closed;
+ ctx->resp.after_closed = client->sock->input;
+ client->sock->input = tmp;
+ ctx->client = NULL;
+ ctx->resp.has_content = 1;
+ } else if (client->sock->input->size != 0) {
+ ctx->resp.has_content = 1;
+ }
+
+ if (ctx->resp.has_content) {
+ if (ctx->shortcut_notify_cb != NULL) {
+ ctx->shortcut_notify_cb(ctx->generator);
+ } else if (!mrb_nil_p(ctx->receiver)) {
+ int gc_arena = mrb_gc_arena_save(ctx->generator->ctx->shared->mrb);
+ mrb_value chunk = build_chunk(ctx);
+ h2o_mruby_run_fiber(ctx->generator, detach_receiver(ctx), chunk, NULL);
+ mrb_gc_arena_restore(ctx->generator->ctx->shared->mrb, gc_arena);
+ }
+ }
+ return 0;
+}
+
+static int headers_sort_cb(const void *_x, const void *_y)
+{
+ const h2o_header_t *x = _x, *y = _y;
+
+ if (x->name->len < y->name->len)
+ return -1;
+ if (x->name->len > y->name->len)
+ return 1;
+ return memcmp(x->name->base, y->name->base, x->name->len);
+}
+
+static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *errstr, int minor_version, int status,
+ h2o_iovec_t msg, h2o_header_t *headers, size_t num_headers, int rlen)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = client->data;
+
+ if (errstr != NULL) {
+ if (errstr != h2o_http1client_error_is_eos) {
+ /* error */
+ post_error(ctx, errstr);
+ return NULL;
+ }
+ /* closed without body */
+ ctx->client = NULL;
+ }
+
+ qsort(headers, num_headers, sizeof(headers[0]), headers_sort_cb);
+ post_response(ctx, status, headers, num_headers);
+ return on_body;
+}
+
+static h2o_http1client_head_cb on_connect(h2o_http1client_t *client, const char *errstr, h2o_iovec_t **reqbufs, size_t *reqbufcnt,
+ int *method_is_head)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = client->data;
+
+ if (errstr != NULL) {
+ post_error(ctx, errstr);
+ return NULL;
+ }
+
+ *reqbufs = h2o_mem_alloc_pool(&ctx->generator->req->pool, sizeof(**reqbufs) * 2);
+ **reqbufs = h2o_iovec_init(ctx->req.buf->bytes, ctx->req.buf->size);
+ *reqbufcnt = 1;
+ if (ctx->req.body.base != NULL)
+ (*reqbufs)[(*reqbufcnt)++] = ctx->req.body;
+ *method_is_head = ctx->req.method_is_head;
+ return on_head;
+}
+
+static inline void append_to_buffer(h2o_buffer_t **buf, const void *src, size_t len)
+{
+ memcpy((*buf)->bytes + (*buf)->size, src, len);
+ (*buf)->size += len;
+}
+
+static int flatten_request_header(h2o_mruby_context_t *handler_ctx, h2o_iovec_t name, h2o_iovec_t value, void *_ctx)
+{
+ struct st_h2o_mruby_http_request_context_t *ctx = _ctx;
+
+ /* ignore certain headers */
+ if (h2o_lcstris(name.base, name.len, H2O_STRLIT("content-length")) ||
+ h2o_lcstris(name.base, name.len, H2O_STRLIT("connection")) || h2o_lcstris(name.base, name.len, H2O_STRLIT("host")))
+ return 0;
+
+ /* mark the existence of transfer-encoding in order to prevent us from adding content-length header */
+ if (h2o_lcstris(name.base, name.len, H2O_STRLIT("transfer-encoding")))
+ ctx->req.has_transfer_encoding = 1;
+
+ h2o_buffer_reserve(&ctx->req.buf, name.len + value.len + sizeof(": \r\n") - 1);
+ append_to_buffer(&ctx->req.buf, name.base, name.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT(": "));
+ append_to_buffer(&ctx->req.buf, value.base, value.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n"));
+
+ return 0;
+}
+
+static mrb_value http_request_method(mrb_state *mrb, mrb_value self)
+{
+ h2o_mruby_generator_t *generator;
+ struct st_h2o_mruby_http_request_context_t *ctx;
+ const char *arg_url;
+ mrb_int arg_url_len;
+ mrb_value arg_hash;
+ h2o_iovec_t method;
+ h2o_url_t url;
+
+ /* parse args */
+ arg_hash = mrb_nil_value();
+ mrb_get_args(mrb, "s|H", &arg_url, &arg_url_len, &arg_hash);
+
+ /* precond check */
+ if ((generator = h2o_mruby_current_generator) == NULL || generator->req == NULL)
+ mrb_exc_raise(mrb, create_downstream_closed_exception(mrb));
+
+ /* allocate context and initialize */
+ ctx = h2o_mem_alloc_shared(&generator->req->pool, sizeof(*ctx), on_dispose);
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->generator = generator;
+ ctx->receiver = mrb_nil_value();
+ h2o_buffer_init(&ctx->req.buf, &h2o_socket_buffer_prototype);
+ h2o_buffer_init(&ctx->resp.after_closed, &h2o_socket_buffer_prototype);
+ ctx->refs.request = mrb_nil_value();
+ ctx->refs.input_stream = mrb_nil_value();
+
+ /* uri */
+ if (h2o_url_parse(arg_url, arg_url_len, &url) != 0)
+ mrb_raise(mrb, E_ARGUMENT_ERROR, "invaild URL");
+
+ /* method */
+ method = h2o_iovec_init(H2O_STRLIT("GET"));
+ if (mrb_hash_p(arg_hash)) {
+ mrb_value t = mrb_hash_get(mrb, arg_hash, mrb_symbol_value(generator->ctx->shared->symbols.sym_method));
+ if (!mrb_nil_p(t)) {
+ t = mrb_str_to_str(mrb, t);
+ method = h2o_iovec_init(RSTRING_PTR(t), RSTRING_LEN(t));
+ if (h2o_memis(method.base, method.len, H2O_STRLIT("HEAD"))) {
+ ctx->req.method_is_head = 1;
+ }
+ }
+ }
+
+ /* start building the request */
+ h2o_buffer_reserve(&ctx->req.buf, method.len + 1);
+ append_to_buffer(&ctx->req.buf, method.base, method.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT(" "));
+ h2o_buffer_reserve(&ctx->req.buf,
+ url.path.len + url.authority.len + sizeof(" HTTP/1.1\r\nConnection: close\r\nHost: \r\n") - 1);
+ append_to_buffer(&ctx->req.buf, url.path.base, url.path.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT(" HTTP/1.1\r\nConnection: close\r\nHost: "));
+ append_to_buffer(&ctx->req.buf, url.authority.base, url.authority.len);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n"));
+
+ /* headers */
+ if (mrb_hash_p(arg_hash)) {
+ mrb_value headers = mrb_hash_get(mrb, arg_hash, mrb_symbol_value(generator->ctx->shared->symbols.sym_headers));
+ if (!mrb_nil_p(headers)) {
+ if (h2o_mruby_iterate_headers(generator->ctx, headers, flatten_request_header, ctx) != 0) {
+ mrb_value exc = mrb_obj_value(mrb->exc);
+ mrb->exc = NULL;
+ mrb_exc_raise(mrb, exc);
+ }
+ }
+ }
+ /* body */
+ if (mrb_hash_p(arg_hash)) {
+ mrb_value body = mrb_hash_get(mrb, arg_hash, mrb_symbol_value(generator->ctx->shared->symbols.sym_body));
+ if (!mrb_nil_p(body)) {
+ if (mrb_obj_eq(mrb, body, generator->rack_input)) {
+ /* fast path */
+ mrb_int pos;
+ mrb_input_stream_get_data(mrb, body, NULL, NULL, &pos, NULL, NULL);
+ ctx->req.body = generator->req->entity;
+ ctx->req.body.base += pos;
+ ctx->req.body.len -= pos;
+ } else {
+ if (!mrb_string_p(body)) {
+ body = mrb_funcall(mrb, body, "read", 0);
+ if (!mrb_string_p(body))
+ mrb_raise(mrb, E_ARGUMENT_ERROR, "body.read did not return string");
+ }
+ ctx->req.body = h2o_strdup(&ctx->generator->req->pool, RSTRING_PTR(body), RSTRING_LEN(body));
+ }
+ if (!ctx->req.has_transfer_encoding) {
+ char buf[64];
+ size_t l = (size_t)sprintf(buf, "content-length: %zu\r\n", ctx->req.body.len);
+ h2o_buffer_reserve(&ctx->req.buf, l);
+ append_to_buffer(&ctx->req.buf, buf, l);
+ }
+ }
+ }
+
+ h2o_buffer_reserve(&ctx->req.buf, 2);
+ append_to_buffer(&ctx->req.buf, H2O_STRLIT("\r\n"));
+
+ /* build request and connect */
+ ctx->refs.request = h2o_mruby_create_data_instance(
+ mrb, mrb_ary_entry(generator->ctx->shared->constants, H2O_MRUBY_HTTP_REQUEST_CLASS), ctx, &request_type);
+ h2o_http1client_connect(&ctx->client, ctx, &generator->req->conn->ctx->proxy.client_ctx, url.host, h2o_url_get_port(&url),
+ url.scheme == &H2O_URL_SCHEME_HTTPS, on_connect);
+
+ return ctx->refs.request;
+}
+
+mrb_value h2o_mruby_http_join_response_callback(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value args,
+ int *next_action)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ struct st_h2o_mruby_http_request_context_t *ctx;
+
+ if (generator->req == NULL)
+ return create_downstream_closed_exception(mrb);
+
+ if ((ctx = mrb_data_check_get_ptr(mrb, mrb_ary_entry(args, 0), &request_type)) == NULL)
+ return mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "HttpRequest#join wrong self");
+
+ ctx->receiver = receiver;
+ *next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_ASYNC;
+ return mrb_nil_value();
+}
+
+mrb_value h2o_mruby_http_fetch_chunk_callback(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value args,
+ int *next_action)
+{
+ mrb_state *mrb = generator->ctx->shared->mrb;
+ struct st_h2o_mruby_http_request_context_t *ctx;
+ mrb_value ret;
+
+ if (generator->req == NULL)
+ return create_downstream_closed_exception(mrb);
+
+ if ((ctx = mrb_data_check_get_ptr(mrb, mrb_ary_entry(args, 0), &input_stream_type)) == NULL)
+ return mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "_HttpInputStream#each wrong self");
+
+ if (ctx->resp.has_content) {
+ ret = build_chunk(ctx);
+ } else {
+ ctx->receiver = receiver;
+ *next_action = H2O_MRUBY_CALLBACK_NEXT_ACTION_ASYNC;
+ ret = mrb_nil_value();
+ }
+
+ return ret;
+}
+
+h2o_mruby_http_request_context_t *h2o_mruby_http_set_shortcut(mrb_state *mrb, mrb_value obj, void (*cb)(h2o_mruby_generator_t *))
+{
+ struct st_h2o_mruby_http_request_context_t *ctx;
+
+ if ((ctx = mrb_data_check_get_ptr(mrb, obj, &input_stream_type)) == NULL)
+ return NULL;
+ ctx->shortcut_notify_cb = cb;
+ return ctx;
+}
+
+h2o_buffer_t **h2o_mruby_http_peek_content(h2o_mruby_http_request_context_t *ctx, int *is_final)
+{
+ *is_final = ctx->client == NULL;
+ return ctx->client != NULL && ctx->resp.has_content ? &ctx->client->sock->input : &ctx->resp.after_closed;
+}
+
+void h2o_mruby_http_request_init_context(h2o_mruby_shared_context_t *ctx)
+{
+ mrb_state *mrb = ctx->mrb;
+
+ h2o_mruby_eval_expr(mrb, H2O_MRUBY_CODE_HTTP_REQUEST);
+ h2o_mruby_assert(mrb);
+
+ struct RClass *module, *klass;
+ module = mrb_define_module(mrb, "H2O");
+
+ mrb_define_method(mrb, mrb->kernel_module, "http_request", http_request_method, MRB_ARGS_ARG(1, 2));
+
+ klass = mrb_class_get_under(mrb, module, "HttpRequest");
+ mrb_ary_set(mrb, ctx->constants, H2O_MRUBY_HTTP_REQUEST_CLASS, mrb_obj_value(klass));
+
+ klass = mrb_class_get_under(mrb, module, "HttpInputStream");
+ mrb_ary_set(mrb, ctx->constants, H2O_MRUBY_HTTP_INPUT_STREAM_CLASS, mrb_obj_value(klass));
+
+ klass = mrb_class_get_under(mrb, klass, "Empty");
+ mrb_ary_set(mrb, ctx->constants, H2O_MRUBY_HTTP_EMPTY_INPUT_STREAM_CLASS, mrb_obj_value(klass));
+
+ h2o_mruby_define_callback(mrb, "_h2o__http_join_response", H2O_MRUBY_CALLBACK_ID_HTTP_JOIN_RESPONSE);
+ h2o_mruby_define_callback(mrb, "_h2o__http_fetch_chunk", H2O_MRUBY_CALLBACK_ID_HTTP_FETCH_CHUNK);
+}