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