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