xref: /unit/src/ruby/nxt_ruby.c (revision 981:e9253a35f47a)
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     nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query,
446                       r->query_length);
447     nxt_ruby_add_sptr(hash_env, NL("SERVER_PROTOCOL"), &r->version,
448                       r->version_length);
449     nxt_ruby_add_sptr(hash_env, NL("REMOTE_ADDR"), &r->remote,
450                       r->remote_length);
451     nxt_ruby_add_sptr(hash_env, NL("SERVER_ADDR"), &r->local, r->local_length);
452 
453     nxt_ruby_add_sptr(hash_env, NL("SERVER_NAME"), &r->server_name,
454                       r->server_name_length);
455     nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2);
456 
457     for (i = 0; i < r->fields_count; i++) {
458         f = r->fields + i;
459 
460         nxt_ruby_add_sptr(hash_env, nxt_unit_sptr_get(&f->name), f->name_length,
461                           &f->value, f->value_length);
462     }
463 
464     if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
465         f = r->fields + r->content_length_field;
466 
467         nxt_ruby_add_sptr(hash_env, NL("CONTENT_LENGTH"),
468                           &f->value, f->value_length);
469     }
470 
471     if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
472         f = r->fields + r->content_type_field;
473 
474         nxt_ruby_add_sptr(hash_env, NL("CONTENT_TYPE"),
475                           &f->value, f->value_length);
476     }
477 
478 #undef NL
479 
480     return NXT_UNIT_OK;
481 }
482 
483 
484 nxt_inline void
485 nxt_ruby_add_sptr(VALUE hash_env,
486     const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len)
487 {
488     char  *str;
489 
490     str = nxt_unit_sptr_get(sptr);
491 
492     rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len));
493 }
494 
495 
496 nxt_inline void
497 nxt_ruby_add_str(VALUE hash_env,
498     const char *name, uint32_t name_len, const char *str, uint32_t len)
499 {
500     rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len));
501 }
502 
503 
504 static nxt_int_t
505 nxt_ruby_rack_result_status(VALUE result)
506 {
507     VALUE   status;
508 
509     status = rb_ary_entry(result, 0);
510 
511     if (TYPE(status) == T_FIXNUM) {
512         return FIX2INT(status);
513     }
514 
515     if (TYPE(status) == T_STRING) {
516         return nxt_int_parse((u_char *) RSTRING_PTR(status),
517                              RSTRING_LEN(status));
518     }
519 
520     nxt_unit_req_error(nxt_ruby_run_ctx.req, "Ruby: Invalid response 'status' "
521                        "format from application");
522 
523     return -2;
524 }
525 
526 
527 typedef struct {
528     int       rc;
529     uint32_t  fields;
530     uint32_t  size;
531 } nxt_ruby_headers_info_t;
532 
533 
534 static int
535 nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status)
536 {
537     int                      rc;
538     VALUE                    headers;
539     nxt_ruby_headers_info_t  headers_info;
540 
541     headers = rb_ary_entry(result, 1);
542     if (nxt_slow_path(TYPE(headers) != T_HASH)) {
543         nxt_unit_req_error(nxt_ruby_run_ctx.req,
544                            "Ruby: Invalid response 'headers' format from "
545                            "application");
546 
547         return NXT_UNIT_ERROR;
548     }
549 
550     rc = NXT_UNIT_OK;
551 
552     headers_info.rc = NXT_UNIT_OK;
553     headers_info.fields = 0;
554     headers_info.size = 0;
555 
556     rb_hash_foreach(headers, nxt_ruby_hash_info,
557                     (VALUE) (uintptr_t) &headers_info);
558     if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) {
559         return headers_info.rc;
560     }
561 
562     rc = nxt_unit_response_init(nxt_ruby_run_ctx.req, status,
563                                 headers_info.fields, headers_info.size);
564     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
565         return rc;
566     }
567 
568     rb_hash_foreach(headers, nxt_ruby_hash_add, (VALUE) (uintptr_t) &rc);
569 
570     return rc;
571 }
572 
573 
574 static int
575 nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg)
576 {
577     const char               *value, *value_end, *pos;
578     nxt_ruby_headers_info_t  *headers_info;
579 
580     headers_info = (void *) (uintptr_t) arg;
581 
582     if (nxt_slow_path(TYPE(r_key) != T_STRING)) {
583         nxt_unit_req_error(nxt_ruby_run_ctx.req,
584                            "Ruby: Wrong header entry 'key' from application");
585 
586         goto fail;
587     }
588 
589     if (nxt_slow_path(TYPE(r_value) != T_STRING)) {
590         nxt_unit_req_error(nxt_ruby_run_ctx.req,
591                            "Ruby: Wrong header entry 'value' from application");
592 
593         goto fail;
594     }
595 
596     value = RSTRING_PTR(r_value);
597     value_end = value + RSTRING_LEN(r_value);
598 
599     pos = value;
600 
601     for ( ;; ) {
602         pos = strchr(pos, '\n');
603 
604         if (pos == NULL) {
605             break;
606         }
607 
608         headers_info->fields++;
609         headers_info->size += RSTRING_LEN(r_key) + (pos - value);
610 
611         pos++;
612         value = pos;
613     }
614 
615     if (value <= value_end) {
616         headers_info->fields++;
617         headers_info->size += RSTRING_LEN(r_key) + (value_end - value);
618     }
619 
620     return ST_CONTINUE;
621 
622 fail:
623 
624     headers_info->rc = NXT_UNIT_ERROR;
625 
626     return ST_STOP;
627 }
628 
629 
630 static int
631 nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg)
632 {
633     int         *rc;
634     uint32_t    key_len;
635     const char  *value, *value_end, *pos;
636 
637     rc = (int *) (uintptr_t) arg;
638 
639     value = RSTRING_PTR(r_value);
640     value_end = value + RSTRING_LEN(r_value);
641 
642     key_len = RSTRING_LEN(r_key);
643 
644     pos = value;
645 
646     for ( ;; ) {
647         pos = strchr(pos, '\n');
648 
649         if (pos == NULL) {
650             break;
651         }
652 
653         *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req,
654                                           RSTRING_PTR(r_key), key_len,
655                                           value, pos - value);
656         if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
657             goto fail;
658         }
659 
660         pos++;
661         value = pos;
662     }
663 
664     if (value <= value_end) {
665         *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req,
666                                           RSTRING_PTR(r_key), key_len,
667                                           value, value_end - value);
668         if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
669             goto fail;
670         }
671     }
672 
673     return ST_CONTINUE;
674 
675 fail:
676 
677     *rc = NXT_UNIT_ERROR;
678 
679     return ST_STOP;
680 }
681 
682 
683 static int
684 nxt_ruby_rack_result_body(VALUE result)
685 {
686     int    rc;
687     VALUE  fn, body;
688 
689     body = rb_ary_entry(result, 2);
690 
691     if (rb_respond_to(body, rb_intern("to_path"))) {
692 
693         fn = rb_funcall(body, rb_intern("to_path"), 0);
694         if (nxt_slow_path(TYPE(fn) != T_STRING)) {
695             nxt_unit_req_error(nxt_ruby_run_ctx.req,
696                                "Ruby: Failed to get 'body' file path from "
697                                "application");
698 
699             return NXT_UNIT_ERROR;
700         }
701 
702         rc = nxt_ruby_rack_result_body_file_write(fn);
703         if (nxt_slow_path(rc != NXT_UNIT_OK)) {
704             return rc;
705         }
706 
707     } else if (rb_respond_to(body, rb_intern("each"))) {
708         rb_iterate(rb_each, body, nxt_ruby_rack_result_body_each, 0);
709 
710     } else {
711         nxt_unit_req_error(nxt_ruby_run_ctx.req,
712                            "Ruby: Invalid response 'body' format "
713                            "from application");
714 
715         return NXT_UNIT_ERROR;
716     }
717 
718     if (rb_respond_to(body, rb_intern("close"))) {
719         rb_funcall(body, rb_intern("close"), 0);
720     }
721 
722     return NXT_UNIT_OK;
723 }
724 
725 
726 typedef struct {
727     int    fd;
728     off_t  pos;
729     off_t  rest;
730 } nxt_ruby_rack_file_t;
731 
732 
733 static ssize_t
734 nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size)
735 {
736     ssize_t               res;
737     nxt_ruby_rack_file_t  *file;
738 
739     file = read_info->data;
740 
741     size = nxt_min(size, (size_t) file->rest);
742 
743     res = pread(file->fd, dst, size, file->pos);
744 
745     if (res >= 0) {
746         file->pos += res;
747         file->rest -= res;
748 
749         if (size > (size_t) res) {
750             file->rest = 0;
751         }
752     }
753 
754     read_info->eof = file->rest == 0;
755 
756     return res;
757 }
758 
759 
760 static int
761 nxt_ruby_rack_result_body_file_write(VALUE filepath)
762 {
763     int                   fd, rc;
764     struct stat           finfo;
765     nxt_ruby_rack_file_t  ruby_file;
766     nxt_unit_read_info_t  read_info;
767 
768     fd = open(RSTRING_PTR(filepath), O_RDONLY, 0);
769     if (nxt_slow_path(fd == -1)) {
770         nxt_unit_req_error(nxt_ruby_run_ctx.req,
771                            "Ruby: Failed to open content file \"%s\": %s (%d)",
772                            RSTRING_PTR(filepath), strerror(errno), errno);
773 
774         return NXT_UNIT_ERROR;
775     }
776 
777     rc = fstat(fd, &finfo);
778     if (nxt_slow_path(rc == -1)) {
779         nxt_unit_req_error(nxt_ruby_run_ctx.req,
780                            "Ruby: Content file fstat(\"%s\") failed: %s (%d)",
781                            RSTRING_PTR(filepath), strerror(errno), errno);
782 
783         close(fd);
784 
785         return NXT_UNIT_ERROR;
786     }
787 
788     ruby_file.fd = fd;
789     ruby_file.pos = 0;
790     ruby_file.rest = finfo.st_size;
791 
792     read_info.read = nxt_ruby_rack_file_read;
793     read_info.eof = ruby_file.rest == 0;
794     read_info.buf_size = ruby_file.rest;
795     read_info.data = &ruby_file;
796 
797     rc = nxt_unit_response_write_cb(nxt_ruby_run_ctx.req, &read_info);
798     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
799         nxt_unit_req_error(nxt_ruby_run_ctx.req,
800                            "Ruby: Failed to write content file.");
801     }
802 
803     close(fd);
804 
805     return rc;
806 }
807 
808 
809 static VALUE
810 nxt_ruby_rack_result_body_each(VALUE body)
811 {
812     int  rc;
813 
814     if (TYPE(body) != T_STRING) {
815         return Qnil;
816     }
817 
818     rc = nxt_unit_response_write(nxt_ruby_run_ctx.req, RSTRING_PTR(body),
819                                  RSTRING_LEN(body));
820     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
821         nxt_unit_req_error(nxt_ruby_run_ctx.req,
822                            "Ruby: Failed to write 'body' from application");
823     }
824 
825     return Qnil;
826 }
827 
828 
829 static void
830 nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, const char *desc)
831 {
832     int    i;
833     VALUE  err, ary, eclass, msg;
834 
835     if (task != NULL) {
836         nxt_log(task, level, "Ruby: %s", desc);
837 
838     } else {
839         nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s", desc);
840     }
841 
842     err = rb_errinfo();
843     if (nxt_slow_path(err == Qnil)) {
844         return;
845     }
846 
847     ary = rb_funcall(err, rb_intern("backtrace"), 0);
848     if (nxt_slow_path(RARRAY_LEN(ary) == 0)) {
849         return;
850     }
851 
852     eclass = rb_class_name(rb_class_of(err));
853     msg = rb_funcall(err, rb_intern("message"), 0);
854 
855     if (task != NULL) {
856         nxt_log(task, level, "Ruby: %s: %s (%s)",
857                 RSTRING_PTR(RARRAY_PTR(ary)[0]),
858                 RSTRING_PTR(msg), RSTRING_PTR(eclass));
859 
860     } else {
861         nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s: %s (%s)",
862                      RSTRING_PTR(RARRAY_PTR(ary)[0]),
863                      RSTRING_PTR(msg), RSTRING_PTR(eclass));
864     }
865 
866     for (i = 1; i < RARRAY_LEN(ary); i++) {
867         if (task != NULL) {
868             nxt_log(task, level, "from %s", RSTRING_PTR(RARRAY_PTR(ary)[i]));
869 
870         } else {
871             nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "from %s",
872                          RSTRING_PTR(RARRAY_PTR(ary)[i]));
873         }
874     }
875 }
876 
877 
878 static void
879 nxt_ruby_atexit(void)
880 {
881     rb_gc_unregister_address(&nxt_ruby_io_input);
882     rb_gc_unregister_address(&nxt_ruby_io_error);
883 
884     rb_gc_unregister_address(&nxt_ruby_rackup);
885     rb_gc_unregister_address(&nxt_ruby_call);
886     rb_gc_unregister_address(&nxt_ruby_env);
887 
888     ruby_cleanup(0);
889 }
890