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