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