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