nxt_ruby.c (1503:c21230ef5a0e) nxt_ruby.c (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>
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
10
11#include NXT_RUBY_MOUNTS_H
12
12
13
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
25typedef struct {
26 nxt_task_t *task;
27 nxt_str_t *script;
28 VALUE builder;
29} nxt_ruby_rack_init_t;
30
31
32static nxt_int_t nxt_ruby_start(nxt_task_t *task,
33 nxt_process_data_t *data);
34static VALUE nxt_ruby_init_basic(VALUE arg);
35static nxt_int_t nxt_ruby_init_io(nxt_task_t *task);
36static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
37
38static VALUE nxt_ruby_require_rubygems(VALUE arg);
39static VALUE nxt_ruby_bundler_setup(VALUE arg);
40static VALUE nxt_ruby_require_rack(VALUE arg);
41static VALUE nxt_ruby_rack_parse_script(VALUE ctx);
42static VALUE nxt_ruby_rack_env_create(VALUE arg);
43static void nxt_ruby_request_handler(nxt_unit_request_info_t *req);
44
45static VALUE nxt_ruby_rack_app_run(VALUE arg);
46static int nxt_ruby_read_request(VALUE hash_env);
47nxt_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);
49nxt_inline void nxt_ruby_add_str(VALUE hash_env,
50 const char *name, uint32_t name_len, const char *str, uint32_t len);
51static nxt_int_t nxt_ruby_rack_result_status(VALUE result);
52static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status);
53static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
54static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg);
55static int nxt_ruby_rack_result_body(VALUE result);
56static int nxt_ruby_rack_result_body_file_write(VALUE filepath);
57static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg,
58 int argc, const VALUE *argv, VALUE blockarg);
59
60static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level,
61 const char *desc);
62
63static void nxt_ruby_atexit(void);
64
65
66static uint32_t compat[] = {
67 NXT_VERNUM, NXT_DEBUG,
68};
69
70static VALUE nxt_ruby_rackup;
71static VALUE nxt_ruby_call;
72static VALUE nxt_ruby_env;
73static VALUE nxt_ruby_io_input;
74static VALUE nxt_ruby_io_error;
75static nxt_ruby_run_ctx_t nxt_ruby_run_ctx;
76
77NXT_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
89static nxt_int_t
90nxt_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
168static VALUE
169nxt_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
197static nxt_int_t
198nxt_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
229static VALUE
230nxt_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
282static VALUE
283nxt_ruby_require_rubygems(VALUE arg)
284{
285 return rb_funcall(rb_cObject, rb_intern("require"), 1,
286 rb_str_new2("rubygems"));
287}
288
289
290static VALUE
291nxt_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
298static VALUE
299nxt_ruby_require_rack(VALUE arg)
300{
301 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
302}
303
304
305static VALUE
306nxt_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
324static VALUE
325nxt_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
354static void
355nxt_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
370static VALUE
371nxt_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
428fail:
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
439static int
440nxt_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
497nxt_inline void
498nxt_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
509nxt_inline void
510nxt_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
517static nxt_int_t
518nxt_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
540typedef struct {
541 int rc;
542 uint32_t fields;
543 uint32_t size;
544} nxt_ruby_headers_info_t;
545
546
547static int
548nxt_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
587static int
588nxt_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
635fail:
636
637 headers_info->rc = NXT_UNIT_ERROR;
638
639 return ST_STOP;
640}
641
642
643static int
644nxt_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
688fail:
689
690 *rc = NXT_UNIT_ERROR;
691
692 return ST_STOP;
693}
694
695
696static int
697nxt_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
740typedef struct {
741 int fd;
742 off_t pos;
743 off_t rest;
744} nxt_ruby_rack_file_t;
745
746
747static ssize_t
748nxt_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
774static int
775nxt_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
823static VALUE
824nxt_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
844static void
845nxt_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
893static void
894nxt_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}
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
26typedef struct {
27 nxt_task_t *task;
28 nxt_str_t *script;
29 VALUE builder;
30} nxt_ruby_rack_init_t;
31
32
33static nxt_int_t nxt_ruby_start(nxt_task_t *task,
34 nxt_process_data_t *data);
35static VALUE nxt_ruby_init_basic(VALUE arg);
36static nxt_int_t nxt_ruby_init_io(nxt_task_t *task);
37static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
38
39static VALUE nxt_ruby_require_rubygems(VALUE arg);
40static VALUE nxt_ruby_bundler_setup(VALUE arg);
41static VALUE nxt_ruby_require_rack(VALUE arg);
42static VALUE nxt_ruby_rack_parse_script(VALUE ctx);
43static VALUE nxt_ruby_rack_env_create(VALUE arg);
44static void nxt_ruby_request_handler(nxt_unit_request_info_t *req);
45
46static VALUE nxt_ruby_rack_app_run(VALUE arg);
47static int nxt_ruby_read_request(VALUE hash_env);
48nxt_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);
50nxt_inline void nxt_ruby_add_str(VALUE hash_env,
51 const char *name, uint32_t name_len, const char *str, uint32_t len);
52static nxt_int_t nxt_ruby_rack_result_status(VALUE result);
53static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status);
54static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
55static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg);
56static int nxt_ruby_rack_result_body(VALUE result);
57static int nxt_ruby_rack_result_body_file_write(VALUE filepath);
58static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg,
59 int argc, const VALUE *argv, VALUE blockarg);
60
61static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level,
62 const char *desc);
63
64static void nxt_ruby_atexit(void);
65
66
67static uint32_t compat[] = {
68 NXT_VERNUM, NXT_DEBUG,
69};
70
71static VALUE nxt_ruby_rackup;
72static VALUE nxt_ruby_call;
73static VALUE nxt_ruby_env;
74static VALUE nxt_ruby_io_input;
75static VALUE nxt_ruby_io_error;
76static nxt_ruby_run_ctx_t nxt_ruby_run_ctx;
77
78NXT_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
90static nxt_int_t
91nxt_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
169static VALUE
170nxt_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
198static nxt_int_t
199nxt_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
230static VALUE
231nxt_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
283static VALUE
284nxt_ruby_require_rubygems(VALUE arg)
285{
286 return rb_funcall(rb_cObject, rb_intern("require"), 1,
287 rb_str_new2("rubygems"));
288}
289
290
291static VALUE
292nxt_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
299static VALUE
300nxt_ruby_require_rack(VALUE arg)
301{
302 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
303}
304
305
306static VALUE
307nxt_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
325static VALUE
326nxt_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
355static void
356nxt_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
371static VALUE
372nxt_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
429fail:
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
440static int
441nxt_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
498nxt_inline void
499nxt_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
510nxt_inline void
511nxt_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
518static nxt_int_t
519nxt_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
541typedef struct {
542 int rc;
543 uint32_t fields;
544 uint32_t size;
545} nxt_ruby_headers_info_t;
546
547
548static int
549nxt_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
588static int
589nxt_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
636fail:
637
638 headers_info->rc = NXT_UNIT_ERROR;
639
640 return ST_STOP;
641}
642
643
644static int
645nxt_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
689fail:
690
691 *rc = NXT_UNIT_ERROR;
692
693 return ST_STOP;
694}
695
696
697static int
698nxt_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
741typedef struct {
742 int fd;
743 off_t pos;
744 off_t rest;
745} nxt_ruby_rack_file_t;
746
747
748static ssize_t
749nxt_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
775static int
776nxt_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
824static VALUE
825nxt_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
845static void
846nxt_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
894static void
895nxt_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}