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