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