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