xref: /unit/src/ruby/nxt_ruby.c (revision 1686:9f31e1ab6c19)
1 /*
2  * Copyright (C) Alexander Borisov
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 #include <ruby/nxt_ruby.h>
7 
8 #include <nxt_unit.h>
9 #include <nxt_unit_request.h>
10 
11 #include NXT_RUBY_MOUNTS_H
12 
13 
14 #define NXT_RUBY_RACK_API_VERSION_MAJOR  1
15 #define NXT_RUBY_RACK_API_VERSION_MINOR  3
16 
17 
18 typedef struct {
19     nxt_task_t  *task;
20     nxt_str_t   *script;
21     VALUE       builder;
22 } nxt_ruby_rack_init_t;
23 
24 
25 static nxt_int_t nxt_ruby_start(nxt_task_t *task,
26     nxt_process_data_t *data);
27 static VALUE nxt_ruby_init_basic(VALUE arg);
28 static nxt_int_t nxt_ruby_init_io(nxt_task_t *task);
29 static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
30 
31 static VALUE nxt_ruby_require_rubygems(VALUE arg);
32 static VALUE nxt_ruby_bundler_setup(VALUE arg);
33 static VALUE nxt_ruby_require_rack(VALUE arg);
34 static VALUE nxt_ruby_rack_parse_script(VALUE ctx);
35 static VALUE nxt_ruby_rack_env_create(VALUE arg);
36 static void nxt_ruby_request_handler(nxt_unit_request_info_t *req);
37 
38 static VALUE nxt_ruby_rack_app_run(VALUE arg);
39 static int nxt_ruby_read_request(VALUE hash_env);
40 nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
41     nxt_unit_sptr_t *sptr, uint32_t len);
42 static nxt_int_t nxt_ruby_rack_result_status(VALUE result);
43 static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status);
44 static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
45 static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg);
46 static int nxt_ruby_rack_result_body(VALUE result);
47 static int nxt_ruby_rack_result_body_file_write(VALUE filepath);
48 static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg,
49     int argc, const VALUE *argv, VALUE blockarg);
50 
51 static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level,
52     const char *desc);
53 
54 static void nxt_ruby_atexit(void);
55 
56 
57 static uint32_t  compat[] = {
58     NXT_VERNUM, NXT_DEBUG,
59 };
60 
61 static VALUE               nxt_ruby_rackup;
62 static VALUE               nxt_ruby_call;
63 static VALUE               nxt_ruby_env;
64 static VALUE               nxt_ruby_io_input;
65 static VALUE               nxt_ruby_io_error;
66 static nxt_ruby_run_ctx_t  nxt_ruby_run_ctx;
67 
68 NXT_EXPORT nxt_app_module_t  nxt_app_module = {
69     sizeof(compat),
70     compat,
71     nxt_string("ruby"),
72     ruby_version,
73     nxt_ruby_mounts,
74     nxt_nitems(nxt_ruby_mounts),
75     NULL,
76     nxt_ruby_start,
77 };
78 
79 typedef struct {
80     nxt_str_t  string;
81     VALUE      *v;
82 } nxt_ruby_string_t;
83 
84 static VALUE  nxt_rb_80_str;
85 static VALUE  nxt_rb_content_length_str;
86 static VALUE  nxt_rb_content_type_str;
87 static VALUE  nxt_rb_http_str;
88 static VALUE  nxt_rb_https_str;
89 static VALUE  nxt_rb_path_info_str;
90 static VALUE  nxt_rb_query_string_str;
91 static VALUE  nxt_rb_rack_url_scheme_str;
92 static VALUE  nxt_rb_remote_addr_str;
93 static VALUE  nxt_rb_request_method_str;
94 static VALUE  nxt_rb_request_uri_str;
95 static VALUE  nxt_rb_server_addr_str;
96 static VALUE  nxt_rb_server_name_str;
97 static VALUE  nxt_rb_server_port_str;
98 static VALUE  nxt_rb_server_protocol_str;
99 
100 static nxt_ruby_string_t nxt_rb_strings[] = {
101     { nxt_string("80"), &nxt_rb_80_str },
102     { nxt_string("CONTENT_LENGTH"), &nxt_rb_content_length_str },
103     { nxt_string("CONTENT_TYPE"), &nxt_rb_content_type_str },
104     { nxt_string("http"), &nxt_rb_http_str },
105     { nxt_string("https"), &nxt_rb_https_str },
106     { nxt_string("PATH_INFO"), &nxt_rb_path_info_str },
107     { nxt_string("QUERY_STRING"), &nxt_rb_query_string_str },
108     { nxt_string("rack.url_scheme"), &nxt_rb_rack_url_scheme_str },
109     { nxt_string("REMOTE_ADDR"), &nxt_rb_remote_addr_str },
110     { nxt_string("REQUEST_METHOD"), &nxt_rb_request_method_str },
111     { nxt_string("REQUEST_URI"), &nxt_rb_request_uri_str },
112     { nxt_string("SERVER_ADDR"), &nxt_rb_server_addr_str },
113     { nxt_string("SERVER_NAME"), &nxt_rb_server_name_str },
114     { nxt_string("SERVER_PORT"), &nxt_rb_server_port_str },
115     { nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str },
116     { nxt_null_string, NULL },
117 };
118 
119 
120 static int
121 nxt_ruby_init_strings(void)
122 {
123     VALUE              v;
124     nxt_ruby_string_t  *pstr;
125 
126     pstr = nxt_rb_strings;
127 
128     while (pstr->string.start != NULL) {
129         v = rb_str_new_static((char *) pstr->string.start, pstr->string.length);
130 
131         if (nxt_slow_path(v == Qnil)) {
132             nxt_unit_alert(NULL, "Ruby: failed to create const string '%.*s'",
133                            (int) pstr->string.length,
134                            (char *) pstr->string.start);
135 
136             return NXT_UNIT_ERROR;
137         }
138 
139         *pstr->v = v;
140 
141         rb_gc_register_address(pstr->v);
142 
143         pstr++;
144     }
145 
146     return NXT_UNIT_OK;
147 }
148 
149 
150 static void
151 nxt_ruby_done_strings(void)
152 {
153     nxt_ruby_string_t  *pstr;
154 
155     pstr = nxt_rb_strings;
156 
157     while (pstr->string.start != NULL) {
158         rb_gc_unregister_address(pstr->v);
159 
160         *pstr->v = Qnil;
161 
162         pstr++;
163     }
164 }
165 
166 
167 static nxt_int_t
168 nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
169 {
170     int                    state, rc;
171     VALUE                  res;
172     nxt_unit_ctx_t         *unit_ctx;
173     nxt_unit_init_t        ruby_unit_init;
174     nxt_ruby_rack_init_t   rack_init;
175     nxt_common_app_conf_t  *conf;
176 
177     static char  *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" };
178 
179     conf = data->app;
180 
181     RUBY_INIT_STACK
182     ruby_init();
183     ruby_options(2, argv);
184     ruby_script("NGINX_Unit");
185 
186     rack_init.task = task;
187     rack_init.script = &conf->u.ruby.script;
188 
189     nxt_ruby_init_strings();
190 
191     res = rb_protect(nxt_ruby_init_basic,
192                      (VALUE) (uintptr_t) &rack_init, &state);
193     if (nxt_slow_path(res == Qnil || state != 0)) {
194         nxt_ruby_exception_log(task, NXT_LOG_ALERT,
195                                "Failed to init basic variables");
196         return NXT_ERROR;
197     }
198 
199     nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init);
200     if (nxt_slow_path(nxt_ruby_rackup == Qnil)) {
201         return NXT_ERROR;
202     }
203 
204     nxt_ruby_call = rb_intern("call");
205     if (nxt_slow_path(nxt_ruby_call == Qnil)) {
206         nxt_alert(task, "Ruby: Unable to find rack entry point");
207 
208         return NXT_ERROR;
209     }
210 
211     nxt_ruby_env = rb_protect(nxt_ruby_rack_env_create, Qnil, &state);
212     if (nxt_slow_path(state != 0)) {
213         nxt_ruby_exception_log(task, NXT_LOG_ALERT,
214                                "Failed to create 'environ' variable");
215         return NXT_ERROR;
216     }
217 
218     rb_gc_register_address(&nxt_ruby_rackup);
219     rb_gc_register_address(&nxt_ruby_call);
220     rb_gc_register_address(&nxt_ruby_env);
221 
222     nxt_unit_default_init(task, &ruby_unit_init);
223 
224     ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler;
225     ruby_unit_init.shm_limit = conf->shm_limit;
226 
227     unit_ctx = nxt_unit_init(&ruby_unit_init);
228     if (nxt_slow_path(unit_ctx == NULL)) {
229         return NXT_ERROR;
230     }
231 
232     nxt_ruby_run_ctx.unit_ctx = unit_ctx;
233 
234     rc = nxt_unit_run(unit_ctx);
235 
236     nxt_ruby_atexit();
237 
238     nxt_ruby_run_ctx.unit_ctx = NULL;
239 
240     nxt_unit_done(unit_ctx);
241 
242     exit(rc);
243 
244     return NXT_OK;
245 }
246 
247 
248 static VALUE
249 nxt_ruby_init_basic(VALUE arg)
250 {
251     int                   state;
252     nxt_int_t             rc;
253     nxt_ruby_rack_init_t  *rack_init;
254 
255     rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg;
256 
257     state = rb_enc_find_index("encdb");
258     if (nxt_slow_path(state == 0)) {
259         nxt_alert(rack_init->task,
260                   "Ruby: Failed to find encoding index 'encdb'");
261 
262         return Qnil;
263     }
264 
265     rb_funcall(rb_cObject, rb_intern("require"), 1,
266                rb_str_new2("enc/trans/transdb"));
267 
268     rc = nxt_ruby_init_io(rack_init->task);
269     if (nxt_slow_path(rc != NXT_OK)) {
270         return Qnil;
271     }
272 
273     return arg;
274 }
275 
276 
277 static nxt_int_t
278 nxt_ruby_init_io(nxt_task_t *task)
279 {
280     VALUE  rb, io_input, io_error;
281 
282     io_input = nxt_ruby_stream_io_input_init();
283     rb = Data_Wrap_Struct(io_input, 0, 0, &nxt_ruby_run_ctx);
284 
285     nxt_ruby_io_input = rb_funcall(io_input, rb_intern("new"), 1, rb);
286     if (nxt_slow_path(nxt_ruby_io_input == Qnil)) {
287         nxt_alert(task, "Ruby: Failed to create environment 'rack.input' var");
288 
289         return NXT_ERROR;
290     }
291 
292     io_error = nxt_ruby_stream_io_error_init();
293     rb = Data_Wrap_Struct(io_error, 0, 0, &nxt_ruby_run_ctx);
294 
295     nxt_ruby_io_error = rb_funcall(io_error, rb_intern("new"), 1, rb);
296     if (nxt_slow_path(nxt_ruby_io_error == Qnil)) {
297         nxt_alert(task, "Ruby: Failed to create environment 'rack.error' var");
298 
299         return NXT_ERROR;
300     }
301 
302     rb_gc_register_address(&nxt_ruby_io_input);
303     rb_gc_register_address(&nxt_ruby_io_error);
304 
305     return NXT_OK;
306 }
307 
308 
309 static VALUE
310 nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init)
311 {
312     int    state;
313     VALUE  rack, rackup, err;
314 
315     rb_protect(nxt_ruby_require_rubygems, Qnil, &state);
316     if (nxt_slow_path(state != 0)) {
317         nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT,
318                                "Failed to require 'rubygems' package");
319         return Qnil;
320     }
321 
322     rb_protect(nxt_ruby_bundler_setup, Qnil, &state);
323     if (state != 0) {
324         err = rb_errinfo();
325 
326         if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) {
327             nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT,
328                                    "Failed to require 'bundler/setup' package");
329             return Qnil;
330         }
331 
332         rb_set_errinfo(Qnil);
333     }
334 
335     rb_protect(nxt_ruby_require_rack, Qnil, &state);
336     if (nxt_slow_path(state != 0)) {
337         nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT,
338                                "Failed to require 'rack' package");
339         return Qnil;
340     }
341 
342     rack = rb_const_get(rb_cObject, rb_intern("Rack"));
343     rack_init->builder = rb_const_get(rack, rb_intern("Builder"));
344 
345     rackup = rb_protect(nxt_ruby_rack_parse_script,
346                         (VALUE) (uintptr_t) rack_init, &state);
347     if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) {
348         nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT,
349                                "Failed to parse rack script");
350         return Qnil;
351     }
352 
353     if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) {
354         nxt_alert(rack_init->task, "Ruby: Invalid rack config file");
355         return Qnil;
356     }
357 
358     return RARRAY_PTR(rackup)[0];
359 }
360 
361 
362 static VALUE
363 nxt_ruby_require_rubygems(VALUE arg)
364 {
365     return rb_funcall(rb_cObject, rb_intern("require"), 1,
366                       rb_str_new2("rubygems"));
367 }
368 
369 
370 static VALUE
371 nxt_ruby_bundler_setup(VALUE arg)
372 {
373     return rb_funcall(rb_cObject, rb_intern("require"), 1,
374                       rb_str_new2("bundler/setup"));
375 }
376 
377 
378 static VALUE
379 nxt_ruby_require_rack(VALUE arg)
380 {
381     return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
382 }
383 
384 
385 static VALUE
386 nxt_ruby_rack_parse_script(VALUE ctx)
387 {
388     VALUE                 script, res;
389     nxt_ruby_rack_init_t  *rack_init;
390 
391     rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx;
392 
393     script = rb_str_new((const char *) rack_init->script->start,
394                         (long) rack_init->script->length);
395 
396     res = rb_funcall(rack_init->builder, rb_intern("parse_file"), 1, script);
397 
398     rb_str_free(script);
399 
400     return res;
401 }
402 
403 
404 static VALUE
405 nxt_ruby_rack_env_create(VALUE arg)
406 {
407     VALUE  hash_env, version;
408 
409     hash_env = rb_hash_new();
410 
411     rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"),
412                  rb_str_new((const char *) nxt_server.start,
413                             (long) nxt_server.length));
414 
415     version = rb_ary_new();
416 
417     rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR));
418     rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR));
419 
420     rb_hash_aset(hash_env, rb_str_new2("rack.version"), version);
421     rb_hash_aset(hash_env, rb_str_new2("rack.input"), nxt_ruby_io_input);
422     rb_hash_aset(hash_env, rb_str_new2("rack.errors"), nxt_ruby_io_error);
423     rb_hash_aset(hash_env, rb_str_new2("rack.multithread"), Qfalse);
424     rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue);
425     rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse);
426     rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse);
427     rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil);
428     rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil);
429 
430     return hash_env;
431 }
432 
433 
434 static void
435 nxt_ruby_request_handler(nxt_unit_request_info_t *req)
436 {
437     int    state;
438     VALUE  res;
439 
440     nxt_ruby_run_ctx.req = req;
441 
442     res = rb_protect(nxt_ruby_rack_app_run, Qnil, &state);
443     if (nxt_slow_path(res == Qnil || state != 0)) {
444         nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
445                                "Failed to run ruby script");
446     }
447 }
448 
449 
450 static VALUE
451 nxt_ruby_rack_app_run(VALUE arg)
452 {
453     int        rc;
454     VALUE      env, result;
455     nxt_int_t  status;
456 
457     env = rb_hash_dup(nxt_ruby_env);
458 
459     rc = nxt_ruby_read_request(env);
460     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
461         nxt_unit_req_alert(nxt_ruby_run_ctx.req,
462                            "Ruby: Failed to process incoming request");
463 
464         goto fail;
465     }
466 
467     result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env);
468     if (nxt_slow_path(TYPE(result) != T_ARRAY)) {
469         nxt_unit_req_error(nxt_ruby_run_ctx.req,
470                            "Ruby: Invalid response format from application");
471 
472         goto fail;
473     }
474 
475     if (nxt_slow_path(RARRAY_LEN(result) != 3)) {
476         nxt_unit_req_error(nxt_ruby_run_ctx.req,
477                            "Ruby: Invalid response format from application. "
478                            "Need 3 entries [Status, Headers, Body]");
479 
480         goto fail;
481     }
482 
483     status = nxt_ruby_rack_result_status(result);
484     if (nxt_slow_path(status < 0)) {
485         nxt_unit_req_error(nxt_ruby_run_ctx.req,
486                            "Ruby: Invalid response status from application.");
487 
488         goto fail;
489     }
490 
491     rc = nxt_ruby_rack_result_headers(result, status);
492     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
493         goto fail;
494     }
495 
496     rc = nxt_ruby_rack_result_body(result);
497     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
498         goto fail;
499     }
500 
501     nxt_unit_request_done(nxt_ruby_run_ctx.req, rc);
502     nxt_ruby_run_ctx.req = NULL;
503 
504     rb_hash_delete(env, rb_obj_id(env));
505 
506     return result;
507 
508 fail:
509 
510     nxt_unit_request_done(nxt_ruby_run_ctx.req, NXT_UNIT_ERROR);
511     nxt_ruby_run_ctx.req = NULL;
512 
513     rb_hash_delete(env, rb_obj_id(env));
514 
515     return Qnil;
516 }
517 
518 
519 static int
520 nxt_ruby_read_request(VALUE hash_env)
521 {
522     VALUE               name;
523     uint32_t            i;
524     nxt_unit_field_t    *f;
525     nxt_unit_request_t  *r;
526 
527     r = nxt_ruby_run_ctx.req->request;
528 
529     nxt_ruby_add_sptr(hash_env, nxt_rb_request_method_str, &r->method,
530                       r->method_length);
531     nxt_ruby_add_sptr(hash_env, nxt_rb_request_uri_str, &r->target,
532                       r->target_length);
533     nxt_ruby_add_sptr(hash_env, nxt_rb_path_info_str, &r->path, r->path_length);
534     nxt_ruby_add_sptr(hash_env, nxt_rb_query_string_str, &r->query,
535                       r->query_length);
536     nxt_ruby_add_sptr(hash_env, nxt_rb_server_protocol_str, &r->version,
537                       r->version_length);
538     nxt_ruby_add_sptr(hash_env, nxt_rb_remote_addr_str, &r->remote,
539                       r->remote_length);
540     nxt_ruby_add_sptr(hash_env, nxt_rb_server_addr_str, &r->local,
541                       r->local_length);
542     nxt_ruby_add_sptr(hash_env, nxt_rb_server_name_str, &r->server_name,
543                       r->server_name_length);
544 
545     rb_hash_aset(hash_env, nxt_rb_server_port_str, nxt_rb_80_str);
546 
547     rb_hash_aset(hash_env, nxt_rb_rack_url_scheme_str,
548                  r->tls ? nxt_rb_https_str : nxt_rb_http_str);
549 
550     for (i = 0; i < r->fields_count; i++) {
551         f = r->fields + i;
552 
553         name = rb_str_new(nxt_unit_sptr_get(&f->name), f->name_length);
554 
555         nxt_ruby_add_sptr(hash_env, name, &f->value, f->value_length);
556     }
557 
558     if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
559         f = r->fields + r->content_length_field;
560 
561         nxt_ruby_add_sptr(hash_env, nxt_rb_content_length_str,
562                           &f->value, f->value_length);
563     }
564 
565     if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
566         f = r->fields + r->content_type_field;
567 
568         nxt_ruby_add_sptr(hash_env, nxt_rb_content_type_str,
569                           &f->value, f->value_length);
570     }
571 
572     return NXT_UNIT_OK;
573 }
574 
575 
576 nxt_inline void
577 nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
578     nxt_unit_sptr_t *sptr, uint32_t len)
579 {
580     char  *str;
581 
582     str = nxt_unit_sptr_get(sptr);
583 
584     rb_hash_aset(hash_env, name, rb_str_new(str, len));
585 }
586 
587 
588 static nxt_int_t
589 nxt_ruby_rack_result_status(VALUE result)
590 {
591     VALUE   status;
592 
593     status = rb_ary_entry(result, 0);
594 
595     if (TYPE(status) == T_FIXNUM) {
596         return FIX2INT(status);
597     }
598 
599     if (TYPE(status) == T_STRING) {
600         return nxt_int_parse((u_char *) RSTRING_PTR(status),
601                              RSTRING_LEN(status));
602     }
603 
604     nxt_unit_req_error(nxt_ruby_run_ctx.req, "Ruby: Invalid response 'status' "
605                        "format from application");
606 
607     return -2;
608 }
609 
610 
611 typedef struct {
612     int       rc;
613     uint32_t  fields;
614     uint32_t  size;
615 } nxt_ruby_headers_info_t;
616 
617 
618 static int
619 nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status)
620 {
621     int                      rc;
622     VALUE                    headers;
623     nxt_ruby_headers_info_t  headers_info;
624 
625     headers = rb_ary_entry(result, 1);
626     if (nxt_slow_path(TYPE(headers) != T_HASH)) {
627         nxt_unit_req_error(nxt_ruby_run_ctx.req,
628                            "Ruby: Invalid response 'headers' format from "
629                            "application");
630 
631         return NXT_UNIT_ERROR;
632     }
633 
634     rc = NXT_UNIT_OK;
635 
636     headers_info.rc = NXT_UNIT_OK;
637     headers_info.fields = 0;
638     headers_info.size = 0;
639 
640     rb_hash_foreach(headers, nxt_ruby_hash_info,
641                     (VALUE) (uintptr_t) &headers_info);
642     if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) {
643         return headers_info.rc;
644     }
645 
646     rc = nxt_unit_response_init(nxt_ruby_run_ctx.req, status,
647                                 headers_info.fields, headers_info.size);
648     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
649         return rc;
650     }
651 
652     rb_hash_foreach(headers, nxt_ruby_hash_add, (VALUE) (uintptr_t) &rc);
653 
654     return rc;
655 }
656 
657 
658 static int
659 nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg)
660 {
661     const char               *value, *value_end, *pos;
662     nxt_ruby_headers_info_t  *headers_info;
663 
664     headers_info = (void *) (uintptr_t) arg;
665 
666     if (nxt_slow_path(TYPE(r_key) != T_STRING)) {
667         nxt_unit_req_error(nxt_ruby_run_ctx.req,
668                            "Ruby: Wrong header entry 'key' from application");
669 
670         goto fail;
671     }
672 
673     if (nxt_slow_path(TYPE(r_value) != T_STRING)) {
674         nxt_unit_req_error(nxt_ruby_run_ctx.req,
675                            "Ruby: Wrong header entry 'value' from application");
676 
677         goto fail;
678     }
679 
680     value = RSTRING_PTR(r_value);
681     value_end = value + RSTRING_LEN(r_value);
682 
683     pos = value;
684 
685     for ( ;; ) {
686         pos = strchr(pos, '\n');
687 
688         if (pos == NULL) {
689             break;
690         }
691 
692         headers_info->fields++;
693         headers_info->size += RSTRING_LEN(r_key) + (pos - value);
694 
695         pos++;
696         value = pos;
697     }
698 
699     if (value <= value_end) {
700         headers_info->fields++;
701         headers_info->size += RSTRING_LEN(r_key) + (value_end - value);
702     }
703 
704     return ST_CONTINUE;
705 
706 fail:
707 
708     headers_info->rc = NXT_UNIT_ERROR;
709 
710     return ST_STOP;
711 }
712 
713 
714 static int
715 nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg)
716 {
717     int         *rc;
718     uint32_t    key_len;
719     const char  *value, *value_end, *pos;
720 
721     rc = (int *) (uintptr_t) arg;
722 
723     value = RSTRING_PTR(r_value);
724     value_end = value + RSTRING_LEN(r_value);
725 
726     key_len = RSTRING_LEN(r_key);
727 
728     pos = value;
729 
730     for ( ;; ) {
731         pos = strchr(pos, '\n');
732 
733         if (pos == NULL) {
734             break;
735         }
736 
737         *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req,
738                                           RSTRING_PTR(r_key), key_len,
739                                           value, pos - value);
740         if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
741             goto fail;
742         }
743 
744         pos++;
745         value = pos;
746     }
747 
748     if (value <= value_end) {
749         *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req,
750                                           RSTRING_PTR(r_key), key_len,
751                                           value, value_end - value);
752         if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
753             goto fail;
754         }
755     }
756 
757     return ST_CONTINUE;
758 
759 fail:
760 
761     *rc = NXT_UNIT_ERROR;
762 
763     return ST_STOP;
764 }
765 
766 
767 static int
768 nxt_ruby_rack_result_body(VALUE result)
769 {
770     int    rc;
771     VALUE  fn, body;
772 
773     body = rb_ary_entry(result, 2);
774 
775     if (rb_respond_to(body, rb_intern("to_path"))) {
776 
777         fn = rb_funcall(body, rb_intern("to_path"), 0);
778         if (nxt_slow_path(TYPE(fn) != T_STRING)) {
779             nxt_unit_req_error(nxt_ruby_run_ctx.req,
780                                "Ruby: Failed to get 'body' file path from "
781                                "application");
782 
783             return NXT_UNIT_ERROR;
784         }
785 
786         rc = nxt_ruby_rack_result_body_file_write(fn);
787         if (nxt_slow_path(rc != NXT_UNIT_OK)) {
788             return rc;
789         }
790 
791     } else if (rb_respond_to(body, rb_intern("each"))) {
792         rb_block_call(body, rb_intern("each"), 0, 0,
793                       nxt_ruby_rack_result_body_each, 0);
794 
795     } else {
796         nxt_unit_req_error(nxt_ruby_run_ctx.req,
797                            "Ruby: Invalid response 'body' format "
798                            "from application");
799 
800         return NXT_UNIT_ERROR;
801     }
802 
803     if (rb_respond_to(body, rb_intern("close"))) {
804         rb_funcall(body, rb_intern("close"), 0);
805     }
806 
807     return NXT_UNIT_OK;
808 }
809 
810 
811 typedef struct {
812     int    fd;
813     off_t  pos;
814     off_t  rest;
815 } nxt_ruby_rack_file_t;
816 
817 
818 static ssize_t
819 nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size)
820 {
821     ssize_t               res;
822     nxt_ruby_rack_file_t  *file;
823 
824     file = read_info->data;
825 
826     size = nxt_min(size, (size_t) file->rest);
827 
828     res = pread(file->fd, dst, size, file->pos);
829 
830     if (res >= 0) {
831         file->pos += res;
832         file->rest -= res;
833 
834         if (size > (size_t) res) {
835             file->rest = 0;
836         }
837     }
838 
839     read_info->eof = file->rest == 0;
840 
841     return res;
842 }
843 
844 
845 static int
846 nxt_ruby_rack_result_body_file_write(VALUE filepath)
847 {
848     int                   fd, rc;
849     struct stat           finfo;
850     nxt_ruby_rack_file_t  ruby_file;
851     nxt_unit_read_info_t  read_info;
852 
853     fd = open(RSTRING_PTR(filepath), O_RDONLY, 0);
854     if (nxt_slow_path(fd == -1)) {
855         nxt_unit_req_error(nxt_ruby_run_ctx.req,
856                            "Ruby: Failed to open content file \"%s\": %s (%d)",
857                            RSTRING_PTR(filepath), strerror(errno), errno);
858 
859         return NXT_UNIT_ERROR;
860     }
861 
862     rc = fstat(fd, &finfo);
863     if (nxt_slow_path(rc == -1)) {
864         nxt_unit_req_error(nxt_ruby_run_ctx.req,
865                            "Ruby: Content file fstat(\"%s\") failed: %s (%d)",
866                            RSTRING_PTR(filepath), strerror(errno), errno);
867 
868         close(fd);
869 
870         return NXT_UNIT_ERROR;
871     }
872 
873     ruby_file.fd = fd;
874     ruby_file.pos = 0;
875     ruby_file.rest = finfo.st_size;
876 
877     read_info.read = nxt_ruby_rack_file_read;
878     read_info.eof = ruby_file.rest == 0;
879     read_info.buf_size = ruby_file.rest;
880     read_info.data = &ruby_file;
881 
882     rc = nxt_unit_response_write_cb(nxt_ruby_run_ctx.req, &read_info);
883     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
884         nxt_unit_req_error(nxt_ruby_run_ctx.req,
885                            "Ruby: Failed to write content file.");
886     }
887 
888     close(fd);
889 
890     return rc;
891 }
892 
893 
894 static VALUE
895 nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc,
896     const VALUE *argv, VALUE blockarg)
897 {
898     int  rc;
899 
900     if (TYPE(body) != T_STRING) {
901         return Qnil;
902     }
903 
904     rc = nxt_unit_response_write(nxt_ruby_run_ctx.req, RSTRING_PTR(body),
905                                  RSTRING_LEN(body));
906     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
907         nxt_unit_req_error(nxt_ruby_run_ctx.req,
908                            "Ruby: Failed to write 'body' from application");
909     }
910 
911     return Qnil;
912 }
913 
914 
915 static void
916 nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, const char *desc)
917 {
918     int    i;
919     VALUE  err, ary, eclass, msg;
920 
921     if (task != NULL) {
922         nxt_log(task, level, "Ruby: %s", desc);
923 
924     } else {
925         nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s", desc);
926     }
927 
928     err = rb_errinfo();
929     if (nxt_slow_path(err == Qnil)) {
930         return;
931     }
932 
933     ary = rb_funcall(err, rb_intern("backtrace"), 0);
934     if (nxt_slow_path(RARRAY_LEN(ary) == 0)) {
935         return;
936     }
937 
938     eclass = rb_class_name(rb_class_of(err));
939     msg = rb_funcall(err, rb_intern("message"), 0);
940 
941     if (task != NULL) {
942         nxt_log(task, level, "Ruby: %s: %s (%s)",
943                 RSTRING_PTR(RARRAY_PTR(ary)[0]),
944                 RSTRING_PTR(msg), RSTRING_PTR(eclass));
945 
946     } else {
947         nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s: %s (%s)",
948                      RSTRING_PTR(RARRAY_PTR(ary)[0]),
949                      RSTRING_PTR(msg), RSTRING_PTR(eclass));
950     }
951 
952     for (i = 1; i < RARRAY_LEN(ary); i++) {
953         if (task != NULL) {
954             nxt_log(task, level, "from %s", RSTRING_PTR(RARRAY_PTR(ary)[i]));
955 
956         } else {
957             nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "from %s",
958                          RSTRING_PTR(RARRAY_PTR(ary)[i]));
959         }
960     }
961 }
962 
963 
964 static void
965 nxt_ruby_atexit(void)
966 {
967     rb_gc_unregister_address(&nxt_ruby_io_input);
968     rb_gc_unregister_address(&nxt_ruby_io_error);
969 
970     rb_gc_unregister_address(&nxt_ruby_rackup);
971     rb_gc_unregister_address(&nxt_ruby_call);
972     rb_gc_unregister_address(&nxt_ruby_env);
973 
974     nxt_ruby_done_strings();
975 
976     ruby_cleanup(0);
977 }
978