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