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