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