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