nxt_ruby.c (1910:b9e844d85f21) nxt_ruby.c (1980:43553aa72111)
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#include <ruby/thread.h>
12
13#include NXT_RUBY_MOUNTS_H
14
15#include <locale.h>
16
17
18#define NXT_RUBY_RACK_API_VERSION_MAJOR 1
19#define NXT_RUBY_RACK_API_VERSION_MINOR 3
20
21
22typedef struct {
23 nxt_task_t *task;
24 nxt_str_t *script;
25 nxt_ruby_ctx_t *rctx;
26} nxt_ruby_rack_init_t;
27
28
29static nxt_int_t nxt_ruby_start(nxt_task_t *task,
30 nxt_process_data_t *data);
31static VALUE nxt_ruby_init_basic(VALUE arg);
32
33static VALUE nxt_ruby_hook_procs_load(VALUE path);
34static VALUE nxt_ruby_hook_register(VALUE arg);
35static VALUE nxt_ruby_hook_call(VALUE name);
36
37static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
38
39static VALUE nxt_ruby_require_rubygems(VALUE arg);
40static VALUE nxt_ruby_bundler_setup(VALUE arg);
41static VALUE nxt_ruby_require_rack(VALUE arg);
42static VALUE nxt_ruby_rack_parse_script(VALUE ctx);
43static VALUE nxt_ruby_rack_env_create(VALUE arg);
44static int nxt_ruby_init_io(nxt_ruby_ctx_t *rctx);
45static void nxt_ruby_request_handler(nxt_unit_request_info_t *req);
46static void *nxt_ruby_request_handler_gvl(void *req);
47static int nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx);
48static void *nxt_ruby_thread_create_gvl(void *rctx);
49static VALUE nxt_ruby_thread_func(VALUE arg);
50static void *nxt_ruby_unit_run(void *ctx);
51static void nxt_ruby_ubf(void *ctx);
52static int nxt_ruby_init_threads(nxt_ruby_app_conf_t *c);
53static void nxt_ruby_join_threads(nxt_unit_ctx_t *ctx,
54 nxt_ruby_app_conf_t *c);
55
56static VALUE nxt_ruby_rack_app_run(VALUE arg);
57static int nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env);
58nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
59 nxt_unit_sptr_t *sptr, uint32_t len);
60static nxt_int_t nxt_ruby_rack_result_status(nxt_unit_request_info_t *req,
61 VALUE result);
62static int nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req,
63 VALUE result, nxt_int_t status);
64static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
65static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg);
66static int nxt_ruby_rack_result_body(nxt_unit_request_info_t *req,
67 VALUE result);
68static int nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req,
69 VALUE filepath);
70static void *nxt_ruby_response_write_cb(void *read_info);
71static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg,
72 int argc, const VALUE *argv, VALUE blockarg);
73static void *nxt_ruby_response_write(void *body);
74
75static void nxt_ruby_exception_log(nxt_unit_request_info_t *req,
76 uint32_t level, const char *desc);
77
78static void nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx);
79static void nxt_ruby_atexit(void);
80
81
82static uint32_t compat[] = {
83 NXT_VERNUM, NXT_DEBUG,
84};
85
86static VALUE nxt_ruby_hook_procs;
87static VALUE nxt_ruby_rackup;
88static VALUE nxt_ruby_call;
89
90static uint32_t nxt_ruby_threads;
91static nxt_ruby_ctx_t *nxt_ruby_ctxs;
92
93NXT_EXPORT nxt_app_module_t nxt_app_module = {
94 sizeof(compat),
95 compat,
96 nxt_string("ruby"),
97 ruby_version,
98 nxt_ruby_mounts,
99 nxt_nitems(nxt_ruby_mounts),
100 NULL,
101 nxt_ruby_start,
102};
103
104typedef struct {
105 nxt_str_t string;
106 VALUE *v;
107} nxt_ruby_string_t;
108
109static VALUE nxt_rb_80_str;
110static VALUE nxt_rb_content_length_str;
111static VALUE nxt_rb_content_type_str;
112static VALUE nxt_rb_http_str;
113static VALUE nxt_rb_https_str;
114static VALUE nxt_rb_path_info_str;
115static VALUE nxt_rb_query_string_str;
116static VALUE nxt_rb_rack_url_scheme_str;
117static VALUE nxt_rb_remote_addr_str;
118static VALUE nxt_rb_request_method_str;
119static VALUE nxt_rb_request_uri_str;
120static VALUE nxt_rb_server_addr_str;
121static VALUE nxt_rb_server_name_str;
122static VALUE nxt_rb_server_port_str;
123static VALUE nxt_rb_server_protocol_str;
124static VALUE nxt_rb_on_worker_boot;
125static VALUE nxt_rb_on_worker_shutdown;
126static VALUE nxt_rb_on_thread_boot;
127static VALUE nxt_rb_on_thread_shutdown;
128
129static nxt_ruby_string_t nxt_rb_strings[] = {
130 { nxt_string("80"), &nxt_rb_80_str },
131 { nxt_string("CONTENT_LENGTH"), &nxt_rb_content_length_str },
132 { nxt_string("CONTENT_TYPE"), &nxt_rb_content_type_str },
133 { nxt_string("http"), &nxt_rb_http_str },
134 { nxt_string("https"), &nxt_rb_https_str },
135 { nxt_string("PATH_INFO"), &nxt_rb_path_info_str },
136 { nxt_string("QUERY_STRING"), &nxt_rb_query_string_str },
137 { nxt_string("rack.url_scheme"), &nxt_rb_rack_url_scheme_str },
138 { nxt_string("REMOTE_ADDR"), &nxt_rb_remote_addr_str },
139 { nxt_string("REQUEST_METHOD"), &nxt_rb_request_method_str },
140 { nxt_string("REQUEST_URI"), &nxt_rb_request_uri_str },
141 { nxt_string("SERVER_ADDR"), &nxt_rb_server_addr_str },
142 { nxt_string("SERVER_NAME"), &nxt_rb_server_name_str },
143 { nxt_string("SERVER_PORT"), &nxt_rb_server_port_str },
144 { nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str },
145 { nxt_string("on_worker_boot"), &nxt_rb_on_worker_boot },
146 { nxt_string("on_worker_shutdown"), &nxt_rb_on_worker_shutdown },
147 { nxt_string("on_thread_boot"), &nxt_rb_on_thread_boot },
148 { nxt_string("on_thread_shutdown"), &nxt_rb_on_thread_shutdown },
149 { nxt_null_string, NULL },
150};
151
152
153static int
154nxt_ruby_init_strings(void)
155{
156 VALUE v;
157 nxt_ruby_string_t *pstr;
158
159 pstr = nxt_rb_strings;
160
161 while (pstr->string.start != NULL) {
162 v = rb_str_new_static((char *) pstr->string.start, pstr->string.length);
163
164 if (nxt_slow_path(v == Qnil)) {
165 nxt_unit_alert(NULL, "Ruby: failed to create const string '%.*s'",
166 (int) pstr->string.length,
167 (char *) pstr->string.start);
168
169 return NXT_UNIT_ERROR;
170 }
171
172 *pstr->v = v;
173
174 rb_gc_register_address(pstr->v);
175
176 pstr++;
177 }
178
179 return NXT_UNIT_OK;
180}
181
182
183static void
184nxt_ruby_done_strings(void)
185{
186 nxt_ruby_string_t *pstr;
187
188 pstr = nxt_rb_strings;
189
190 while (pstr->string.start != NULL) {
191 rb_gc_unregister_address(pstr->v);
192
193 *pstr->v = Qnil;
194
195 pstr++;
196 }
197}
198
199
200static VALUE
201nxt_ruby_hook_procs_load(VALUE path)
202{
203 VALUE module, file, file_obj;
204
205 module = rb_define_module("Unit");
206
207 nxt_ruby_hook_procs = rb_hash_new();
208
209 rb_gc_register_address(&nxt_ruby_hook_procs);
210
211 rb_define_module_function(module, "on_worker_boot",
212 &nxt_ruby_hook_register, 0);
213 rb_define_module_function(module, "on_worker_shutdown",
214 &nxt_ruby_hook_register, 0);
215 rb_define_module_function(module, "on_thread_boot",
216 &nxt_ruby_hook_register, 0);
217 rb_define_module_function(module, "on_thread_shutdown",
218 &nxt_ruby_hook_register, 0);
219
220 file = rb_const_get(rb_cObject, rb_intern("File"));
221 file_obj = rb_funcall(file, rb_intern("read"), 1, path);
222
223 return rb_funcall(module, rb_intern("module_eval"), 3, file_obj, path,
224 INT2NUM(1));
225}
226
227
228static VALUE
229nxt_ruby_hook_register(VALUE arg)
230{
231 VALUE kernel, callee, callee_str;
232
233 rb_need_block();
234
235 kernel = rb_const_get(rb_cObject, rb_intern("Kernel"));
236 callee = rb_funcall(kernel, rb_intern("__callee__"), 0);
237 callee_str = rb_funcall(callee, rb_intern("to_s"), 0);
238
239 rb_hash_aset(nxt_ruby_hook_procs, callee_str, rb_block_proc());
240
241 return Qnil;
242}
243
244
245static VALUE
246nxt_ruby_hook_call(VALUE name)
247{
248 VALUE proc;
249
250 proc = rb_hash_lookup(nxt_ruby_hook_procs, name);
251 if (proc == Qnil) {
252 return Qnil;
253 }
254
255 return rb_funcall(proc, rb_intern("call"), 0);
256}
257
258
259static nxt_int_t
260nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
261{
262 int state, rc;
263 VALUE res, path;
264 nxt_ruby_ctx_t ruby_ctx;
265 nxt_unit_ctx_t *unit_ctx;
266 nxt_unit_init_t ruby_unit_init;
267 nxt_ruby_app_conf_t *c;
268 nxt_ruby_rack_init_t rack_init;
269 nxt_common_app_conf_t *conf;
270
271 static char *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" };
272
273 conf = data->app;
274 c = &conf->u.ruby;
275
276 nxt_ruby_threads = c->threads;
277
278 setlocale(LC_CTYPE, "");
279
280 RUBY_INIT_STACK
281 ruby_init();
282 ruby_options(2, argv);
283 ruby_script("NGINX_Unit");
284
285 ruby_ctx.env = Qnil;
286 ruby_ctx.io_input = Qnil;
287 ruby_ctx.io_error = Qnil;
288 ruby_ctx.thread = Qnil;
289 ruby_ctx.ctx = NULL;
290 ruby_ctx.req = NULL;
291
292 rack_init.task = task;
293 rack_init.script = &c->script;
294 rack_init.rctx = &ruby_ctx;
295
296 nxt_ruby_init_strings();
297
298 res = rb_protect(nxt_ruby_init_basic,
299 (VALUE) (uintptr_t) &rack_init, &state);
300 if (nxt_slow_path(res == Qnil || state != 0)) {
301 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
302 "Failed to init basic variables");
303 return NXT_ERROR;
304 }
305
306 nxt_ruby_call = Qnil;
307 nxt_ruby_hook_procs = Qnil;
308
309 if (c->hooks.start != NULL) {
310 path = rb_str_new((const char *) c->hooks.start,
311 (long) c->hooks.length);
312
313 rb_protect(nxt_ruby_hook_procs_load, path, &state);
314 rb_str_free(path);
315 if (nxt_slow_path(state != 0)) {
316 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
317 "Failed to setup hooks");
318 return NXT_ERROR;
319 }
320 }
321
322 if (nxt_ruby_hook_procs != Qnil) {
323 rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_boot, &state);
324 if (nxt_slow_path(state != 0)) {
325 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
326 "Failed to call on_worker_boot()");
327 return NXT_ERROR;
328 }
329 }
330
331 nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init);
332 if (nxt_slow_path(nxt_ruby_rackup == Qnil)) {
333 return NXT_ERROR;
334 }
335
336 rb_gc_register_address(&nxt_ruby_rackup);
337
338 nxt_ruby_call = rb_intern("call");
339 if (nxt_slow_path(nxt_ruby_call == Qnil)) {
340 nxt_alert(task, "Ruby: Unable to find rack entry point");
341
342 goto fail;
343 }
344
345 rb_gc_register_address(&nxt_ruby_call);
346
347 ruby_ctx.env = rb_protect(nxt_ruby_rack_env_create,
348 (VALUE) (uintptr_t) &ruby_ctx, &state);
349 if (nxt_slow_path(ruby_ctx.env == Qnil || state != 0)) {
350 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
351 "Failed to create 'environ' variable");
352 goto fail;
353 }
354
355 rc = nxt_ruby_init_threads(c);
356 if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
357 goto fail;
358 }
359
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#include <ruby/thread.h>
12
13#include NXT_RUBY_MOUNTS_H
14
15#include <locale.h>
16
17
18#define NXT_RUBY_RACK_API_VERSION_MAJOR 1
19#define NXT_RUBY_RACK_API_VERSION_MINOR 3
20
21
22typedef struct {
23 nxt_task_t *task;
24 nxt_str_t *script;
25 nxt_ruby_ctx_t *rctx;
26} nxt_ruby_rack_init_t;
27
28
29static nxt_int_t nxt_ruby_start(nxt_task_t *task,
30 nxt_process_data_t *data);
31static VALUE nxt_ruby_init_basic(VALUE arg);
32
33static VALUE nxt_ruby_hook_procs_load(VALUE path);
34static VALUE nxt_ruby_hook_register(VALUE arg);
35static VALUE nxt_ruby_hook_call(VALUE name);
36
37static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
38
39static VALUE nxt_ruby_require_rubygems(VALUE arg);
40static VALUE nxt_ruby_bundler_setup(VALUE arg);
41static VALUE nxt_ruby_require_rack(VALUE arg);
42static VALUE nxt_ruby_rack_parse_script(VALUE ctx);
43static VALUE nxt_ruby_rack_env_create(VALUE arg);
44static int nxt_ruby_init_io(nxt_ruby_ctx_t *rctx);
45static void nxt_ruby_request_handler(nxt_unit_request_info_t *req);
46static void *nxt_ruby_request_handler_gvl(void *req);
47static int nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx);
48static void *nxt_ruby_thread_create_gvl(void *rctx);
49static VALUE nxt_ruby_thread_func(VALUE arg);
50static void *nxt_ruby_unit_run(void *ctx);
51static void nxt_ruby_ubf(void *ctx);
52static int nxt_ruby_init_threads(nxt_ruby_app_conf_t *c);
53static void nxt_ruby_join_threads(nxt_unit_ctx_t *ctx,
54 nxt_ruby_app_conf_t *c);
55
56static VALUE nxt_ruby_rack_app_run(VALUE arg);
57static int nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env);
58nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
59 nxt_unit_sptr_t *sptr, uint32_t len);
60static nxt_int_t nxt_ruby_rack_result_status(nxt_unit_request_info_t *req,
61 VALUE result);
62static int nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req,
63 VALUE result, nxt_int_t status);
64static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
65static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg);
66static int nxt_ruby_rack_result_body(nxt_unit_request_info_t *req,
67 VALUE result);
68static int nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req,
69 VALUE filepath);
70static void *nxt_ruby_response_write_cb(void *read_info);
71static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg,
72 int argc, const VALUE *argv, VALUE blockarg);
73static void *nxt_ruby_response_write(void *body);
74
75static void nxt_ruby_exception_log(nxt_unit_request_info_t *req,
76 uint32_t level, const char *desc);
77
78static void nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx);
79static void nxt_ruby_atexit(void);
80
81
82static uint32_t compat[] = {
83 NXT_VERNUM, NXT_DEBUG,
84};
85
86static VALUE nxt_ruby_hook_procs;
87static VALUE nxt_ruby_rackup;
88static VALUE nxt_ruby_call;
89
90static uint32_t nxt_ruby_threads;
91static nxt_ruby_ctx_t *nxt_ruby_ctxs;
92
93NXT_EXPORT nxt_app_module_t nxt_app_module = {
94 sizeof(compat),
95 compat,
96 nxt_string("ruby"),
97 ruby_version,
98 nxt_ruby_mounts,
99 nxt_nitems(nxt_ruby_mounts),
100 NULL,
101 nxt_ruby_start,
102};
103
104typedef struct {
105 nxt_str_t string;
106 VALUE *v;
107} nxt_ruby_string_t;
108
109static VALUE nxt_rb_80_str;
110static VALUE nxt_rb_content_length_str;
111static VALUE nxt_rb_content_type_str;
112static VALUE nxt_rb_http_str;
113static VALUE nxt_rb_https_str;
114static VALUE nxt_rb_path_info_str;
115static VALUE nxt_rb_query_string_str;
116static VALUE nxt_rb_rack_url_scheme_str;
117static VALUE nxt_rb_remote_addr_str;
118static VALUE nxt_rb_request_method_str;
119static VALUE nxt_rb_request_uri_str;
120static VALUE nxt_rb_server_addr_str;
121static VALUE nxt_rb_server_name_str;
122static VALUE nxt_rb_server_port_str;
123static VALUE nxt_rb_server_protocol_str;
124static VALUE nxt_rb_on_worker_boot;
125static VALUE nxt_rb_on_worker_shutdown;
126static VALUE nxt_rb_on_thread_boot;
127static VALUE nxt_rb_on_thread_shutdown;
128
129static nxt_ruby_string_t nxt_rb_strings[] = {
130 { nxt_string("80"), &nxt_rb_80_str },
131 { nxt_string("CONTENT_LENGTH"), &nxt_rb_content_length_str },
132 { nxt_string("CONTENT_TYPE"), &nxt_rb_content_type_str },
133 { nxt_string("http"), &nxt_rb_http_str },
134 { nxt_string("https"), &nxt_rb_https_str },
135 { nxt_string("PATH_INFO"), &nxt_rb_path_info_str },
136 { nxt_string("QUERY_STRING"), &nxt_rb_query_string_str },
137 { nxt_string("rack.url_scheme"), &nxt_rb_rack_url_scheme_str },
138 { nxt_string("REMOTE_ADDR"), &nxt_rb_remote_addr_str },
139 { nxt_string("REQUEST_METHOD"), &nxt_rb_request_method_str },
140 { nxt_string("REQUEST_URI"), &nxt_rb_request_uri_str },
141 { nxt_string("SERVER_ADDR"), &nxt_rb_server_addr_str },
142 { nxt_string("SERVER_NAME"), &nxt_rb_server_name_str },
143 { nxt_string("SERVER_PORT"), &nxt_rb_server_port_str },
144 { nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str },
145 { nxt_string("on_worker_boot"), &nxt_rb_on_worker_boot },
146 { nxt_string("on_worker_shutdown"), &nxt_rb_on_worker_shutdown },
147 { nxt_string("on_thread_boot"), &nxt_rb_on_thread_boot },
148 { nxt_string("on_thread_shutdown"), &nxt_rb_on_thread_shutdown },
149 { nxt_null_string, NULL },
150};
151
152
153static int
154nxt_ruby_init_strings(void)
155{
156 VALUE v;
157 nxt_ruby_string_t *pstr;
158
159 pstr = nxt_rb_strings;
160
161 while (pstr->string.start != NULL) {
162 v = rb_str_new_static((char *) pstr->string.start, pstr->string.length);
163
164 if (nxt_slow_path(v == Qnil)) {
165 nxt_unit_alert(NULL, "Ruby: failed to create const string '%.*s'",
166 (int) pstr->string.length,
167 (char *) pstr->string.start);
168
169 return NXT_UNIT_ERROR;
170 }
171
172 *pstr->v = v;
173
174 rb_gc_register_address(pstr->v);
175
176 pstr++;
177 }
178
179 return NXT_UNIT_OK;
180}
181
182
183static void
184nxt_ruby_done_strings(void)
185{
186 nxt_ruby_string_t *pstr;
187
188 pstr = nxt_rb_strings;
189
190 while (pstr->string.start != NULL) {
191 rb_gc_unregister_address(pstr->v);
192
193 *pstr->v = Qnil;
194
195 pstr++;
196 }
197}
198
199
200static VALUE
201nxt_ruby_hook_procs_load(VALUE path)
202{
203 VALUE module, file, file_obj;
204
205 module = rb_define_module("Unit");
206
207 nxt_ruby_hook_procs = rb_hash_new();
208
209 rb_gc_register_address(&nxt_ruby_hook_procs);
210
211 rb_define_module_function(module, "on_worker_boot",
212 &nxt_ruby_hook_register, 0);
213 rb_define_module_function(module, "on_worker_shutdown",
214 &nxt_ruby_hook_register, 0);
215 rb_define_module_function(module, "on_thread_boot",
216 &nxt_ruby_hook_register, 0);
217 rb_define_module_function(module, "on_thread_shutdown",
218 &nxt_ruby_hook_register, 0);
219
220 file = rb_const_get(rb_cObject, rb_intern("File"));
221 file_obj = rb_funcall(file, rb_intern("read"), 1, path);
222
223 return rb_funcall(module, rb_intern("module_eval"), 3, file_obj, path,
224 INT2NUM(1));
225}
226
227
228static VALUE
229nxt_ruby_hook_register(VALUE arg)
230{
231 VALUE kernel, callee, callee_str;
232
233 rb_need_block();
234
235 kernel = rb_const_get(rb_cObject, rb_intern("Kernel"));
236 callee = rb_funcall(kernel, rb_intern("__callee__"), 0);
237 callee_str = rb_funcall(callee, rb_intern("to_s"), 0);
238
239 rb_hash_aset(nxt_ruby_hook_procs, callee_str, rb_block_proc());
240
241 return Qnil;
242}
243
244
245static VALUE
246nxt_ruby_hook_call(VALUE name)
247{
248 VALUE proc;
249
250 proc = rb_hash_lookup(nxt_ruby_hook_procs, name);
251 if (proc == Qnil) {
252 return Qnil;
253 }
254
255 return rb_funcall(proc, rb_intern("call"), 0);
256}
257
258
259static nxt_int_t
260nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
261{
262 int state, rc;
263 VALUE res, path;
264 nxt_ruby_ctx_t ruby_ctx;
265 nxt_unit_ctx_t *unit_ctx;
266 nxt_unit_init_t ruby_unit_init;
267 nxt_ruby_app_conf_t *c;
268 nxt_ruby_rack_init_t rack_init;
269 nxt_common_app_conf_t *conf;
270
271 static char *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" };
272
273 conf = data->app;
274 c = &conf->u.ruby;
275
276 nxt_ruby_threads = c->threads;
277
278 setlocale(LC_CTYPE, "");
279
280 RUBY_INIT_STACK
281 ruby_init();
282 ruby_options(2, argv);
283 ruby_script("NGINX_Unit");
284
285 ruby_ctx.env = Qnil;
286 ruby_ctx.io_input = Qnil;
287 ruby_ctx.io_error = Qnil;
288 ruby_ctx.thread = Qnil;
289 ruby_ctx.ctx = NULL;
290 ruby_ctx.req = NULL;
291
292 rack_init.task = task;
293 rack_init.script = &c->script;
294 rack_init.rctx = &ruby_ctx;
295
296 nxt_ruby_init_strings();
297
298 res = rb_protect(nxt_ruby_init_basic,
299 (VALUE) (uintptr_t) &rack_init, &state);
300 if (nxt_slow_path(res == Qnil || state != 0)) {
301 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
302 "Failed to init basic variables");
303 return NXT_ERROR;
304 }
305
306 nxt_ruby_call = Qnil;
307 nxt_ruby_hook_procs = Qnil;
308
309 if (c->hooks.start != NULL) {
310 path = rb_str_new((const char *) c->hooks.start,
311 (long) c->hooks.length);
312
313 rb_protect(nxt_ruby_hook_procs_load, path, &state);
314 rb_str_free(path);
315 if (nxt_slow_path(state != 0)) {
316 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
317 "Failed to setup hooks");
318 return NXT_ERROR;
319 }
320 }
321
322 if (nxt_ruby_hook_procs != Qnil) {
323 rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_boot, &state);
324 if (nxt_slow_path(state != 0)) {
325 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
326 "Failed to call on_worker_boot()");
327 return NXT_ERROR;
328 }
329 }
330
331 nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init);
332 if (nxt_slow_path(nxt_ruby_rackup == Qnil)) {
333 return NXT_ERROR;
334 }
335
336 rb_gc_register_address(&nxt_ruby_rackup);
337
338 nxt_ruby_call = rb_intern("call");
339 if (nxt_slow_path(nxt_ruby_call == Qnil)) {
340 nxt_alert(task, "Ruby: Unable to find rack entry point");
341
342 goto fail;
343 }
344
345 rb_gc_register_address(&nxt_ruby_call);
346
347 ruby_ctx.env = rb_protect(nxt_ruby_rack_env_create,
348 (VALUE) (uintptr_t) &ruby_ctx, &state);
349 if (nxt_slow_path(ruby_ctx.env == Qnil || state != 0)) {
350 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
351 "Failed to create 'environ' variable");
352 goto fail;
353 }
354
355 rc = nxt_ruby_init_threads(c);
356 if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
357 goto fail;
358 }
359
360 nxt_unit_default_init(task, &ruby_unit_init);
360 nxt_unit_default_init(task, &ruby_unit_init, conf);
361
362 ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler;
363 ruby_unit_init.callbacks.ready_handler = nxt_ruby_ready_handler;
361
362 ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler;
363 ruby_unit_init.callbacks.ready_handler = nxt_ruby_ready_handler;
364 ruby_unit_init.shm_limit = conf->shm_limit;
365 ruby_unit_init.data = c;
366 ruby_unit_init.ctx_data = &ruby_ctx;
367
368 unit_ctx = nxt_unit_init(&ruby_unit_init);
369 if (nxt_slow_path(unit_ctx == NULL)) {
370 goto fail;
371 }
372
373 if (nxt_ruby_hook_procs != Qnil) {
374 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
375 if (nxt_slow_path(state != 0)) {
376 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
377 "Failed to call on_thread_boot()");
378 }
379 }
380
381 rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_unit_run, unit_ctx,
382 nxt_ruby_ubf, unit_ctx);
383
384 if (nxt_ruby_hook_procs != Qnil) {
385 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
386 if (nxt_slow_path(state != 0)) {
387 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
388 "Failed to call on_thread_shutdown()");
389 }
390 }
391
392 nxt_ruby_join_threads(unit_ctx, c);
393
394 if (nxt_ruby_hook_procs != Qnil) {
395 rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_shutdown, &state);
396 if (nxt_slow_path(state != 0)) {
397 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
398 "Failed to call on_worker_shutdown()");
399 }
400 }
401
402 nxt_unit_done(unit_ctx);
403
404 nxt_ruby_ctx_done(&ruby_ctx);
405
406 nxt_ruby_atexit();
407
408 exit(rc);
409
410 return NXT_OK;
411
412fail:
413
414 nxt_ruby_join_threads(NULL, c);
415
416 nxt_ruby_ctx_done(&ruby_ctx);
417
418 nxt_ruby_atexit();
419
420 return NXT_ERROR;
421}
422
423
424static VALUE
425nxt_ruby_init_basic(VALUE arg)
426{
427 int state;
428 nxt_ruby_rack_init_t *rack_init;
429
430 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg;
431
432 state = rb_enc_find_index("encdb");
433 if (nxt_slow_path(state == 0)) {
434 nxt_alert(rack_init->task,
435 "Ruby: Failed to find encoding index 'encdb'");
436
437 return Qnil;
438 }
439
440 rb_funcall(rb_cObject, rb_intern("require"), 1,
441 rb_str_new2("enc/trans/transdb"));
442
443 return arg;
444}
445
446
447static VALUE
448nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init)
449{
450 int state;
451 VALUE rackup, err;
452
453 rb_protect(nxt_ruby_require_rubygems, Qnil, &state);
454 if (nxt_slow_path(state != 0)) {
455 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
456 "Failed to require 'rubygems' package");
457 return Qnil;
458 }
459
460 rb_protect(nxt_ruby_bundler_setup, Qnil, &state);
461 if (state != 0) {
462 err = rb_errinfo();
463
464 if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) {
465 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
466 "Failed to require 'bundler/setup' package");
467 return Qnil;
468 }
469
470 rb_set_errinfo(Qnil);
471 }
472
473 rb_protect(nxt_ruby_require_rack, Qnil, &state);
474 if (nxt_slow_path(state != 0)) {
475 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
476 "Failed to require 'rack' package");
477 return Qnil;
478 }
479
480 rackup = rb_protect(nxt_ruby_rack_parse_script,
481 (VALUE) (uintptr_t) rack_init, &state);
482 if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) {
483 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
484 "Failed to parse rack script");
485 return Qnil;
486 }
487
488 if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) {
489 nxt_alert(rack_init->task, "Ruby: Invalid rack config file");
490 return Qnil;
491 }
492
493 return RARRAY_PTR(rackup)[0];
494}
495
496
497static VALUE
498nxt_ruby_require_rubygems(VALUE arg)
499{
500 return rb_funcall(rb_cObject, rb_intern("require"), 1,
501 rb_str_new2("rubygems"));
502}
503
504
505static VALUE
506nxt_ruby_bundler_setup(VALUE arg)
507{
508 return rb_funcall(rb_cObject, rb_intern("require"), 1,
509 rb_str_new2("bundler/setup"));
510}
511
512
513static VALUE
514nxt_ruby_require_rack(VALUE arg)
515{
516 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
517}
518
519
520static VALUE
521nxt_ruby_rack_parse_script(VALUE ctx)
522{
523 VALUE script, res, rack, builder;
524 nxt_ruby_rack_init_t *rack_init;
525
526 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx;
527
528 rack = rb_const_get(rb_cObject, rb_intern("Rack"));
529 builder = rb_const_get(rack, rb_intern("Builder"));
530
531 script = rb_str_new((const char *) rack_init->script->start,
532 (long) rack_init->script->length);
533
534 res = rb_funcall(builder, rb_intern("parse_file"), 1, script);
535
536 rb_str_free(script);
537
538 return res;
539}
540
541
542static VALUE
543nxt_ruby_rack_env_create(VALUE arg)
544{
545 int rc;
546 VALUE hash_env, version;
547 nxt_ruby_ctx_t *rctx;
548
549 rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg;
550
551 rc = nxt_ruby_init_io(rctx);
552 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
553 return Qnil;
554 }
555
556 hash_env = rb_hash_new();
557
558 rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"),
559 rb_str_new((const char *) nxt_server.start,
560 (long) nxt_server.length));
561
562 version = rb_ary_new();
563
564 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR));
565 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR));
566
567 rb_hash_aset(hash_env, rb_str_new2("rack.version"), version);
568 rb_hash_aset(hash_env, rb_str_new2("rack.input"), rctx->io_input);
569 rb_hash_aset(hash_env, rb_str_new2("rack.errors"), rctx->io_error);
570 rb_hash_aset(hash_env, rb_str_new2("rack.multithread"),
571 nxt_ruby_threads > 1 ? Qtrue : Qfalse);
572 rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue);
573 rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse);
574 rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse);
575 rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil);
576 rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil);
577
578 rctx->env = hash_env;
579
580 rb_gc_register_address(&rctx->env);
581
582 return hash_env;
583}
584
585
586static int
587nxt_ruby_init_io(nxt_ruby_ctx_t *rctx)
588{
589 VALUE io_input, io_error;
590
591 io_input = nxt_ruby_stream_io_input_init();
592
593 rctx->io_input = rb_funcall(io_input, rb_intern("new"), 1,
594 (VALUE) (uintptr_t) rctx);
595 if (nxt_slow_path(rctx->io_input == Qnil)) {
596 nxt_unit_alert(NULL,
597 "Ruby: Failed to create environment 'rack.input' var");
598
599 return NXT_UNIT_ERROR;
600 }
601
602 rb_gc_register_address(&rctx->io_input);
603
604 io_error = nxt_ruby_stream_io_error_init();
605
606 rctx->io_error = rb_funcall(io_error, rb_intern("new"), 1,
607 (VALUE) (uintptr_t) rctx);
608 if (nxt_slow_path(rctx->io_error == Qnil)) {
609 nxt_unit_alert(NULL,
610 "Ruby: Failed to create environment 'rack.error' var");
611
612 return NXT_UNIT_ERROR;
613 }
614
615 rb_gc_register_address(&rctx->io_error);
616
617 return NXT_UNIT_OK;
618}
619
620
621static void
622nxt_ruby_request_handler(nxt_unit_request_info_t *req)
623{
624 (void) rb_thread_call_with_gvl(nxt_ruby_request_handler_gvl, req);
625}
626
627
628static void *
629nxt_ruby_request_handler_gvl(void *data)
630{
631 int state;
632 VALUE res;
633 nxt_ruby_ctx_t *rctx;
634 nxt_unit_request_info_t *req;
635
636 req = data;
637
638 rctx = req->ctx->data;
639 rctx->req = req;
640
641 res = rb_protect(nxt_ruby_rack_app_run, (VALUE) (uintptr_t) req, &state);
642 if (nxt_slow_path(res == Qnil || state != 0)) {
643 nxt_ruby_exception_log(req, NXT_LOG_ERR,
644 "Failed to run ruby script");
645
646 nxt_unit_request_done(req, NXT_UNIT_ERROR);
647
648 } else {
649 nxt_unit_request_done(req, NXT_UNIT_OK);
650 }
651
652 rctx->req = NULL;
653
654 return NULL;
655}
656
657
658static VALUE
659nxt_ruby_rack_app_run(VALUE arg)
660{
661 int rc;
662 VALUE env, result;
663 nxt_int_t status;
664 nxt_ruby_ctx_t *rctx;
665 nxt_unit_request_info_t *req;
666
667 req = (nxt_unit_request_info_t *) arg;
668
669 rctx = req->ctx->data;
670
671 env = rb_hash_dup(rctx->env);
672
673 rc = nxt_ruby_read_request(req, env);
674 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
675 nxt_unit_req_alert(req,
676 "Ruby: Failed to process incoming request");
677
678 goto fail;
679 }
680
681 result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env);
682 if (nxt_slow_path(TYPE(result) != T_ARRAY)) {
683 nxt_unit_req_error(req,
684 "Ruby: Invalid response format from application");
685
686 goto fail;
687 }
688
689 if (nxt_slow_path(RARRAY_LEN(result) != 3)) {
690 nxt_unit_req_error(req,
691 "Ruby: Invalid response format from application. "
692 "Need 3 entries [Status, Headers, Body]");
693
694 goto fail;
695 }
696
697 status = nxt_ruby_rack_result_status(req, result);
698 if (nxt_slow_path(status < 0)) {
699 nxt_unit_req_error(req,
700 "Ruby: Invalid response status from application.");
701
702 goto fail;
703 }
704
705 rc = nxt_ruby_rack_result_headers(req, result, status);
706 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
707 goto fail;
708 }
709
710 rc = nxt_ruby_rack_result_body(req, result);
711 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
712 goto fail;
713 }
714
715 rb_hash_delete(env, rb_obj_id(env));
716
717 return result;
718
719fail:
720
721 rb_hash_delete(env, rb_obj_id(env));
722
723 return Qnil;
724}
725
726
727static int
728nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env)
729{
730 VALUE name;
731 uint32_t i;
732 nxt_unit_field_t *f;
733 nxt_unit_request_t *r;
734
735 r = req->request;
736
737 nxt_ruby_add_sptr(hash_env, nxt_rb_request_method_str, &r->method,
738 r->method_length);
739 nxt_ruby_add_sptr(hash_env, nxt_rb_request_uri_str, &r->target,
740 r->target_length);
741 nxt_ruby_add_sptr(hash_env, nxt_rb_path_info_str, &r->path, r->path_length);
742 nxt_ruby_add_sptr(hash_env, nxt_rb_query_string_str, &r->query,
743 r->query_length);
744 nxt_ruby_add_sptr(hash_env, nxt_rb_server_protocol_str, &r->version,
745 r->version_length);
746 nxt_ruby_add_sptr(hash_env, nxt_rb_remote_addr_str, &r->remote,
747 r->remote_length);
748 nxt_ruby_add_sptr(hash_env, nxt_rb_server_addr_str, &r->local,
749 r->local_length);
750 nxt_ruby_add_sptr(hash_env, nxt_rb_server_name_str, &r->server_name,
751 r->server_name_length);
752
753 rb_hash_aset(hash_env, nxt_rb_server_port_str, nxt_rb_80_str);
754
755 rb_hash_aset(hash_env, nxt_rb_rack_url_scheme_str,
756 r->tls ? nxt_rb_https_str : nxt_rb_http_str);
757
758 for (i = 0; i < r->fields_count; i++) {
759 f = r->fields + i;
760
761 name = rb_str_new(nxt_unit_sptr_get(&f->name), f->name_length);
762
763 nxt_ruby_add_sptr(hash_env, name, &f->value, f->value_length);
764 }
765
766 if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
767 f = r->fields + r->content_length_field;
768
769 nxt_ruby_add_sptr(hash_env, nxt_rb_content_length_str,
770 &f->value, f->value_length);
771 }
772
773 if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
774 f = r->fields + r->content_type_field;
775
776 nxt_ruby_add_sptr(hash_env, nxt_rb_content_type_str,
777 &f->value, f->value_length);
778 }
779
780 return NXT_UNIT_OK;
781}
782
783
784nxt_inline void
785nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
786 nxt_unit_sptr_t *sptr, uint32_t len)
787{
788 char *str;
789
790 str = nxt_unit_sptr_get(sptr);
791
792 rb_hash_aset(hash_env, name, rb_str_new(str, len));
793}
794
795
796static nxt_int_t
797nxt_ruby_rack_result_status(nxt_unit_request_info_t *req, VALUE result)
798{
799 VALUE status;
800
801 status = rb_ary_entry(result, 0);
802
803 if (TYPE(status) == T_FIXNUM) {
804 return FIX2INT(status);
805 }
806
807 if (TYPE(status) == T_STRING) {
808 return nxt_int_parse((u_char *) RSTRING_PTR(status),
809 RSTRING_LEN(status));
810 }
811
812 nxt_unit_req_error(req, "Ruby: Invalid response 'status' "
813 "format from application");
814
815 return -2;
816}
817
818
819typedef struct {
820 int rc;
821 uint32_t fields;
822 uint32_t size;
823 nxt_unit_request_info_t *req;
824} nxt_ruby_headers_info_t;
825
826
827static int
828nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req, VALUE result,
829 nxt_int_t status)
830{
831 int rc;
832 VALUE headers;
833 nxt_ruby_headers_info_t headers_info;
834
835 headers = rb_ary_entry(result, 1);
836 if (nxt_slow_path(TYPE(headers) != T_HASH)) {
837 nxt_unit_req_error(req,
838 "Ruby: Invalid response 'headers' format from "
839 "application");
840
841 return NXT_UNIT_ERROR;
842 }
843
844 rc = NXT_UNIT_OK;
845
846 headers_info.rc = NXT_UNIT_OK;
847 headers_info.fields = 0;
848 headers_info.size = 0;
849 headers_info.req = req;
850
851 rb_hash_foreach(headers, nxt_ruby_hash_info,
852 (VALUE) (uintptr_t) &headers_info);
853 if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) {
854 return headers_info.rc;
855 }
856
857 rc = nxt_unit_response_init(req, status,
858 headers_info.fields, headers_info.size);
859 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
860 return rc;
861 }
862
863 rb_hash_foreach(headers, nxt_ruby_hash_add,
864 (VALUE) (uintptr_t) &headers_info);
865
866 return rc;
867}
868
869
870static int
871nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg)
872{
873 const char *value, *value_end, *pos;
874 nxt_ruby_headers_info_t *headers_info;
875
876 headers_info = (void *) (uintptr_t) arg;
877
878 if (nxt_slow_path(TYPE(r_key) != T_STRING)) {
879 nxt_unit_req_error(headers_info->req,
880 "Ruby: Wrong header entry 'key' from application");
881
882 goto fail;
883 }
884
885 if (nxt_slow_path(TYPE(r_value) != T_STRING)) {
886 nxt_unit_req_error(headers_info->req,
887 "Ruby: Wrong header entry 'value' from application");
888
889 goto fail;
890 }
891
892 value = RSTRING_PTR(r_value);
893 value_end = value + RSTRING_LEN(r_value);
894
895 pos = value;
896
897 for ( ;; ) {
898 pos = strchr(pos, '\n');
899
900 if (pos == NULL) {
901 break;
902 }
903
904 headers_info->fields++;
905 headers_info->size += RSTRING_LEN(r_key) + (pos - value);
906
907 pos++;
908 value = pos;
909 }
910
911 if (value <= value_end) {
912 headers_info->fields++;
913 headers_info->size += RSTRING_LEN(r_key) + (value_end - value);
914 }
915
916 return ST_CONTINUE;
917
918fail:
919
920 headers_info->rc = NXT_UNIT_ERROR;
921
922 return ST_STOP;
923}
924
925
926static int
927nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg)
928{
929 int *rc;
930 uint32_t key_len;
931 const char *value, *value_end, *pos;
932 nxt_ruby_headers_info_t *headers_info;
933
934 headers_info = (void *) (uintptr_t) arg;
935 rc = &headers_info->rc;
936
937 value = RSTRING_PTR(r_value);
938 value_end = value + RSTRING_LEN(r_value);
939
940 key_len = RSTRING_LEN(r_key);
941
942 pos = value;
943
944 for ( ;; ) {
945 pos = strchr(pos, '\n');
946
947 if (pos == NULL) {
948 break;
949 }
950
951 *rc = nxt_unit_response_add_field(headers_info->req,
952 RSTRING_PTR(r_key), key_len,
953 value, pos - value);
954 if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
955 goto fail;
956 }
957
958 pos++;
959 value = pos;
960 }
961
962 if (value <= value_end) {
963 *rc = nxt_unit_response_add_field(headers_info->req,
964 RSTRING_PTR(r_key), key_len,
965 value, value_end - value);
966 if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
967 goto fail;
968 }
969 }
970
971 return ST_CONTINUE;
972
973fail:
974
975 *rc = NXT_UNIT_ERROR;
976
977 return ST_STOP;
978}
979
980
981static int
982nxt_ruby_rack_result_body(nxt_unit_request_info_t *req, VALUE result)
983{
984 int rc;
985 VALUE fn, body;
986
987 body = rb_ary_entry(result, 2);
988
989 if (rb_respond_to(body, rb_intern("to_path"))) {
990
991 fn = rb_funcall(body, rb_intern("to_path"), 0);
992 if (nxt_slow_path(TYPE(fn) != T_STRING)) {
993 nxt_unit_req_error(req,
994 "Ruby: Failed to get 'body' file path from "
995 "application");
996
997 return NXT_UNIT_ERROR;
998 }
999
1000 rc = nxt_ruby_rack_result_body_file_write(req, fn);
1001 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1002 return rc;
1003 }
1004
1005 } else if (rb_respond_to(body, rb_intern("each"))) {
1006 rb_block_call(body, rb_intern("each"), 0, 0,
1007 nxt_ruby_rack_result_body_each, (VALUE) (uintptr_t) req);
1008
1009 } else {
1010 nxt_unit_req_error(req,
1011 "Ruby: Invalid response 'body' format "
1012 "from application");
1013
1014 return NXT_UNIT_ERROR;
1015 }
1016
1017 if (rb_respond_to(body, rb_intern("close"))) {
1018 rb_funcall(body, rb_intern("close"), 0);
1019 }
1020
1021 return NXT_UNIT_OK;
1022}
1023
1024
1025typedef struct {
1026 int fd;
1027 off_t pos;
1028 off_t rest;
1029} nxt_ruby_rack_file_t;
1030
1031
1032static ssize_t
1033nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size)
1034{
1035 ssize_t res;
1036 nxt_ruby_rack_file_t *file;
1037
1038 file = read_info->data;
1039
1040 size = nxt_min(size, (size_t) file->rest);
1041
1042 res = pread(file->fd, dst, size, file->pos);
1043
1044 if (res >= 0) {
1045 file->pos += res;
1046 file->rest -= res;
1047
1048 if (size > (size_t) res) {
1049 file->rest = 0;
1050 }
1051 }
1052
1053 read_info->eof = file->rest == 0;
1054
1055 return res;
1056}
1057
1058
1059typedef struct {
1060 nxt_unit_read_info_t read_info;
1061 nxt_unit_request_info_t *req;
1062} nxt_ruby_read_info_t;
1063
1064
1065static int
1066nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req,
1067 VALUE filepath)
1068{
1069 int fd, rc;
1070 struct stat finfo;
1071 nxt_ruby_rack_file_t ruby_file;
1072 nxt_ruby_read_info_t ri;
1073
1074 fd = open(RSTRING_PTR(filepath), O_RDONLY, 0);
1075 if (nxt_slow_path(fd == -1)) {
1076 nxt_unit_req_error(req,
1077 "Ruby: Failed to open content file \"%s\": %s (%d)",
1078 RSTRING_PTR(filepath), strerror(errno), errno);
1079
1080 return NXT_UNIT_ERROR;
1081 }
1082
1083 rc = fstat(fd, &finfo);
1084 if (nxt_slow_path(rc == -1)) {
1085 nxt_unit_req_error(req,
1086 "Ruby: Content file fstat(\"%s\") failed: %s (%d)",
1087 RSTRING_PTR(filepath), strerror(errno), errno);
1088
1089 close(fd);
1090
1091 return NXT_UNIT_ERROR;
1092 }
1093
1094 ruby_file.fd = fd;
1095 ruby_file.pos = 0;
1096 ruby_file.rest = finfo.st_size;
1097
1098 ri.read_info.read = nxt_ruby_rack_file_read;
1099 ri.read_info.eof = ruby_file.rest == 0;
1100 ri.read_info.buf_size = ruby_file.rest;
1101 ri.read_info.data = &ruby_file;
1102 ri.req = req;
1103
1104 rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_response_write_cb,
1105 &ri,
1106 nxt_ruby_ubf,
1107 req->ctx);
1108
1109 close(fd);
1110
1111 return rc;
1112}
1113
1114
1115static void *
1116nxt_ruby_response_write_cb(void *data)
1117{
1118 int rc;
1119 nxt_ruby_read_info_t *ri;
1120
1121 ri = data;
1122
1123 rc = nxt_unit_response_write_cb(ri->req, &ri->read_info);
1124 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1125 nxt_unit_req_error(ri->req, "Ruby: Failed to write content file.");
1126 }
1127
1128 return (void *) (intptr_t) rc;
1129}
1130
1131
1132typedef struct {
1133 VALUE body;
1134 nxt_unit_request_info_t *req;
1135} nxt_ruby_write_info_t;
1136
1137
1138static VALUE
1139nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc,
1140 const VALUE *argv, VALUE blockarg)
1141{
1142 nxt_ruby_write_info_t wi;
1143
1144 if (TYPE(body) != T_STRING) {
1145 return Qnil;
1146 }
1147
1148 wi.body = body;
1149 wi.req = (void *) (uintptr_t) arg;
1150
1151 (void) rb_thread_call_without_gvl(nxt_ruby_response_write,
1152 (void *) (uintptr_t) &wi,
1153 nxt_ruby_ubf, wi.req->ctx);
1154
1155 return Qnil;
1156}
1157
1158
1159static void *
1160nxt_ruby_response_write(void *data)
1161{
1162 int rc;
1163 nxt_ruby_write_info_t *wi;
1164
1165 wi = data;
1166
1167 rc = nxt_unit_response_write(wi->req, RSTRING_PTR(wi->body),
1168 RSTRING_LEN(wi->body));
1169 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1170 nxt_unit_req_error(wi->req,
1171 "Ruby: Failed to write 'body' from application");
1172 }
1173
1174 return (void *) (intptr_t) rc;
1175}
1176
1177
1178static void
1179nxt_ruby_exception_log(nxt_unit_request_info_t *req, uint32_t level,
1180 const char *desc)
1181{
1182 int i;
1183 VALUE err, ary, eclass, msg;
1184
1185 nxt_unit_req_log(req, level, "Ruby: %s", desc);
1186
1187 err = rb_errinfo();
1188 if (nxt_slow_path(err == Qnil)) {
1189 return;
1190 }
1191
1192 eclass = rb_class_name(rb_class_of(err));
1193
1194 msg = rb_funcall(err, rb_intern("message"), 0);
1195 ary = rb_funcall(err, rb_intern("backtrace"), 0);
1196
1197 if (RARRAY_LEN(ary) == 0) {
1198 nxt_unit_req_log(req, level, "Ruby: %s (%s)", RSTRING_PTR(msg),
1199 RSTRING_PTR(eclass));
1200
1201 return;
1202 }
1203
1204 nxt_unit_req_log(req, level, "Ruby: %s: %s (%s)",
1205 RSTRING_PTR(RARRAY_PTR(ary)[0]),
1206 RSTRING_PTR(msg), RSTRING_PTR(eclass));
1207
1208 for (i = 1; i < RARRAY_LEN(ary); i++) {
1209 nxt_unit_req_log(req, level, "from %s",
1210 RSTRING_PTR(RARRAY_PTR(ary)[i]));
1211 }
1212}
1213
1214
1215static void
1216nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx)
1217{
1218 if (rctx->io_input != Qnil) {
1219 rb_gc_unregister_address(&rctx->io_input);
1220 }
1221
1222 if (rctx->io_error != Qnil) {
1223 rb_gc_unregister_address(&rctx->io_error);
1224 }
1225
1226 if (rctx->env != Qnil) {
1227 rb_gc_unregister_address(&rctx->env);
1228 }
1229}
1230
1231
1232static void
1233nxt_ruby_atexit(void)
1234{
1235 if (nxt_ruby_rackup != Qnil) {
1236 rb_gc_unregister_address(&nxt_ruby_rackup);
1237 }
1238
1239 if (nxt_ruby_call != Qnil) {
1240 rb_gc_unregister_address(&nxt_ruby_call);
1241 }
1242
1243 if (nxt_ruby_hook_procs != Qnil) {
1244 rb_gc_unregister_address(&nxt_ruby_hook_procs);
1245 }
1246
1247 nxt_ruby_done_strings();
1248
1249 ruby_cleanup(0);
1250}
1251
1252
1253static int
1254nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx)
1255{
1256 VALUE res;
1257 uint32_t i;
1258 nxt_ruby_ctx_t *rctx;
1259 nxt_ruby_app_conf_t *c;
1260
364 ruby_unit_init.data = c;
365 ruby_unit_init.ctx_data = &ruby_ctx;
366
367 unit_ctx = nxt_unit_init(&ruby_unit_init);
368 if (nxt_slow_path(unit_ctx == NULL)) {
369 goto fail;
370 }
371
372 if (nxt_ruby_hook_procs != Qnil) {
373 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
374 if (nxt_slow_path(state != 0)) {
375 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
376 "Failed to call on_thread_boot()");
377 }
378 }
379
380 rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_unit_run, unit_ctx,
381 nxt_ruby_ubf, unit_ctx);
382
383 if (nxt_ruby_hook_procs != Qnil) {
384 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
385 if (nxt_slow_path(state != 0)) {
386 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
387 "Failed to call on_thread_shutdown()");
388 }
389 }
390
391 nxt_ruby_join_threads(unit_ctx, c);
392
393 if (nxt_ruby_hook_procs != Qnil) {
394 rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_shutdown, &state);
395 if (nxt_slow_path(state != 0)) {
396 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
397 "Failed to call on_worker_shutdown()");
398 }
399 }
400
401 nxt_unit_done(unit_ctx);
402
403 nxt_ruby_ctx_done(&ruby_ctx);
404
405 nxt_ruby_atexit();
406
407 exit(rc);
408
409 return NXT_OK;
410
411fail:
412
413 nxt_ruby_join_threads(NULL, c);
414
415 nxt_ruby_ctx_done(&ruby_ctx);
416
417 nxt_ruby_atexit();
418
419 return NXT_ERROR;
420}
421
422
423static VALUE
424nxt_ruby_init_basic(VALUE arg)
425{
426 int state;
427 nxt_ruby_rack_init_t *rack_init;
428
429 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg;
430
431 state = rb_enc_find_index("encdb");
432 if (nxt_slow_path(state == 0)) {
433 nxt_alert(rack_init->task,
434 "Ruby: Failed to find encoding index 'encdb'");
435
436 return Qnil;
437 }
438
439 rb_funcall(rb_cObject, rb_intern("require"), 1,
440 rb_str_new2("enc/trans/transdb"));
441
442 return arg;
443}
444
445
446static VALUE
447nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init)
448{
449 int state;
450 VALUE rackup, err;
451
452 rb_protect(nxt_ruby_require_rubygems, Qnil, &state);
453 if (nxt_slow_path(state != 0)) {
454 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
455 "Failed to require 'rubygems' package");
456 return Qnil;
457 }
458
459 rb_protect(nxt_ruby_bundler_setup, Qnil, &state);
460 if (state != 0) {
461 err = rb_errinfo();
462
463 if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) {
464 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
465 "Failed to require 'bundler/setup' package");
466 return Qnil;
467 }
468
469 rb_set_errinfo(Qnil);
470 }
471
472 rb_protect(nxt_ruby_require_rack, Qnil, &state);
473 if (nxt_slow_path(state != 0)) {
474 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
475 "Failed to require 'rack' package");
476 return Qnil;
477 }
478
479 rackup = rb_protect(nxt_ruby_rack_parse_script,
480 (VALUE) (uintptr_t) rack_init, &state);
481 if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) {
482 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
483 "Failed to parse rack script");
484 return Qnil;
485 }
486
487 if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) {
488 nxt_alert(rack_init->task, "Ruby: Invalid rack config file");
489 return Qnil;
490 }
491
492 return RARRAY_PTR(rackup)[0];
493}
494
495
496static VALUE
497nxt_ruby_require_rubygems(VALUE arg)
498{
499 return rb_funcall(rb_cObject, rb_intern("require"), 1,
500 rb_str_new2("rubygems"));
501}
502
503
504static VALUE
505nxt_ruby_bundler_setup(VALUE arg)
506{
507 return rb_funcall(rb_cObject, rb_intern("require"), 1,
508 rb_str_new2("bundler/setup"));
509}
510
511
512static VALUE
513nxt_ruby_require_rack(VALUE arg)
514{
515 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
516}
517
518
519static VALUE
520nxt_ruby_rack_parse_script(VALUE ctx)
521{
522 VALUE script, res, rack, builder;
523 nxt_ruby_rack_init_t *rack_init;
524
525 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx;
526
527 rack = rb_const_get(rb_cObject, rb_intern("Rack"));
528 builder = rb_const_get(rack, rb_intern("Builder"));
529
530 script = rb_str_new((const char *) rack_init->script->start,
531 (long) rack_init->script->length);
532
533 res = rb_funcall(builder, rb_intern("parse_file"), 1, script);
534
535 rb_str_free(script);
536
537 return res;
538}
539
540
541static VALUE
542nxt_ruby_rack_env_create(VALUE arg)
543{
544 int rc;
545 VALUE hash_env, version;
546 nxt_ruby_ctx_t *rctx;
547
548 rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg;
549
550 rc = nxt_ruby_init_io(rctx);
551 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
552 return Qnil;
553 }
554
555 hash_env = rb_hash_new();
556
557 rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"),
558 rb_str_new((const char *) nxt_server.start,
559 (long) nxt_server.length));
560
561 version = rb_ary_new();
562
563 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR));
564 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR));
565
566 rb_hash_aset(hash_env, rb_str_new2("rack.version"), version);
567 rb_hash_aset(hash_env, rb_str_new2("rack.input"), rctx->io_input);
568 rb_hash_aset(hash_env, rb_str_new2("rack.errors"), rctx->io_error);
569 rb_hash_aset(hash_env, rb_str_new2("rack.multithread"),
570 nxt_ruby_threads > 1 ? Qtrue : Qfalse);
571 rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue);
572 rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse);
573 rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse);
574 rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil);
575 rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil);
576
577 rctx->env = hash_env;
578
579 rb_gc_register_address(&rctx->env);
580
581 return hash_env;
582}
583
584
585static int
586nxt_ruby_init_io(nxt_ruby_ctx_t *rctx)
587{
588 VALUE io_input, io_error;
589
590 io_input = nxt_ruby_stream_io_input_init();
591
592 rctx->io_input = rb_funcall(io_input, rb_intern("new"), 1,
593 (VALUE) (uintptr_t) rctx);
594 if (nxt_slow_path(rctx->io_input == Qnil)) {
595 nxt_unit_alert(NULL,
596 "Ruby: Failed to create environment 'rack.input' var");
597
598 return NXT_UNIT_ERROR;
599 }
600
601 rb_gc_register_address(&rctx->io_input);
602
603 io_error = nxt_ruby_stream_io_error_init();
604
605 rctx->io_error = rb_funcall(io_error, rb_intern("new"), 1,
606 (VALUE) (uintptr_t) rctx);
607 if (nxt_slow_path(rctx->io_error == Qnil)) {
608 nxt_unit_alert(NULL,
609 "Ruby: Failed to create environment 'rack.error' var");
610
611 return NXT_UNIT_ERROR;
612 }
613
614 rb_gc_register_address(&rctx->io_error);
615
616 return NXT_UNIT_OK;
617}
618
619
620static void
621nxt_ruby_request_handler(nxt_unit_request_info_t *req)
622{
623 (void) rb_thread_call_with_gvl(nxt_ruby_request_handler_gvl, req);
624}
625
626
627static void *
628nxt_ruby_request_handler_gvl(void *data)
629{
630 int state;
631 VALUE res;
632 nxt_ruby_ctx_t *rctx;
633 nxt_unit_request_info_t *req;
634
635 req = data;
636
637 rctx = req->ctx->data;
638 rctx->req = req;
639
640 res = rb_protect(nxt_ruby_rack_app_run, (VALUE) (uintptr_t) req, &state);
641 if (nxt_slow_path(res == Qnil || state != 0)) {
642 nxt_ruby_exception_log(req, NXT_LOG_ERR,
643 "Failed to run ruby script");
644
645 nxt_unit_request_done(req, NXT_UNIT_ERROR);
646
647 } else {
648 nxt_unit_request_done(req, NXT_UNIT_OK);
649 }
650
651 rctx->req = NULL;
652
653 return NULL;
654}
655
656
657static VALUE
658nxt_ruby_rack_app_run(VALUE arg)
659{
660 int rc;
661 VALUE env, result;
662 nxt_int_t status;
663 nxt_ruby_ctx_t *rctx;
664 nxt_unit_request_info_t *req;
665
666 req = (nxt_unit_request_info_t *) arg;
667
668 rctx = req->ctx->data;
669
670 env = rb_hash_dup(rctx->env);
671
672 rc = nxt_ruby_read_request(req, env);
673 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
674 nxt_unit_req_alert(req,
675 "Ruby: Failed to process incoming request");
676
677 goto fail;
678 }
679
680 result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env);
681 if (nxt_slow_path(TYPE(result) != T_ARRAY)) {
682 nxt_unit_req_error(req,
683 "Ruby: Invalid response format from application");
684
685 goto fail;
686 }
687
688 if (nxt_slow_path(RARRAY_LEN(result) != 3)) {
689 nxt_unit_req_error(req,
690 "Ruby: Invalid response format from application. "
691 "Need 3 entries [Status, Headers, Body]");
692
693 goto fail;
694 }
695
696 status = nxt_ruby_rack_result_status(req, result);
697 if (nxt_slow_path(status < 0)) {
698 nxt_unit_req_error(req,
699 "Ruby: Invalid response status from application.");
700
701 goto fail;
702 }
703
704 rc = nxt_ruby_rack_result_headers(req, result, status);
705 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
706 goto fail;
707 }
708
709 rc = nxt_ruby_rack_result_body(req, result);
710 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
711 goto fail;
712 }
713
714 rb_hash_delete(env, rb_obj_id(env));
715
716 return result;
717
718fail:
719
720 rb_hash_delete(env, rb_obj_id(env));
721
722 return Qnil;
723}
724
725
726static int
727nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env)
728{
729 VALUE name;
730 uint32_t i;
731 nxt_unit_field_t *f;
732 nxt_unit_request_t *r;
733
734 r = req->request;
735
736 nxt_ruby_add_sptr(hash_env, nxt_rb_request_method_str, &r->method,
737 r->method_length);
738 nxt_ruby_add_sptr(hash_env, nxt_rb_request_uri_str, &r->target,
739 r->target_length);
740 nxt_ruby_add_sptr(hash_env, nxt_rb_path_info_str, &r->path, r->path_length);
741 nxt_ruby_add_sptr(hash_env, nxt_rb_query_string_str, &r->query,
742 r->query_length);
743 nxt_ruby_add_sptr(hash_env, nxt_rb_server_protocol_str, &r->version,
744 r->version_length);
745 nxt_ruby_add_sptr(hash_env, nxt_rb_remote_addr_str, &r->remote,
746 r->remote_length);
747 nxt_ruby_add_sptr(hash_env, nxt_rb_server_addr_str, &r->local,
748 r->local_length);
749 nxt_ruby_add_sptr(hash_env, nxt_rb_server_name_str, &r->server_name,
750 r->server_name_length);
751
752 rb_hash_aset(hash_env, nxt_rb_server_port_str, nxt_rb_80_str);
753
754 rb_hash_aset(hash_env, nxt_rb_rack_url_scheme_str,
755 r->tls ? nxt_rb_https_str : nxt_rb_http_str);
756
757 for (i = 0; i < r->fields_count; i++) {
758 f = r->fields + i;
759
760 name = rb_str_new(nxt_unit_sptr_get(&f->name), f->name_length);
761
762 nxt_ruby_add_sptr(hash_env, name, &f->value, f->value_length);
763 }
764
765 if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
766 f = r->fields + r->content_length_field;
767
768 nxt_ruby_add_sptr(hash_env, nxt_rb_content_length_str,
769 &f->value, f->value_length);
770 }
771
772 if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
773 f = r->fields + r->content_type_field;
774
775 nxt_ruby_add_sptr(hash_env, nxt_rb_content_type_str,
776 &f->value, f->value_length);
777 }
778
779 return NXT_UNIT_OK;
780}
781
782
783nxt_inline void
784nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
785 nxt_unit_sptr_t *sptr, uint32_t len)
786{
787 char *str;
788
789 str = nxt_unit_sptr_get(sptr);
790
791 rb_hash_aset(hash_env, name, rb_str_new(str, len));
792}
793
794
795static nxt_int_t
796nxt_ruby_rack_result_status(nxt_unit_request_info_t *req, VALUE result)
797{
798 VALUE status;
799
800 status = rb_ary_entry(result, 0);
801
802 if (TYPE(status) == T_FIXNUM) {
803 return FIX2INT(status);
804 }
805
806 if (TYPE(status) == T_STRING) {
807 return nxt_int_parse((u_char *) RSTRING_PTR(status),
808 RSTRING_LEN(status));
809 }
810
811 nxt_unit_req_error(req, "Ruby: Invalid response 'status' "
812 "format from application");
813
814 return -2;
815}
816
817
818typedef struct {
819 int rc;
820 uint32_t fields;
821 uint32_t size;
822 nxt_unit_request_info_t *req;
823} nxt_ruby_headers_info_t;
824
825
826static int
827nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req, VALUE result,
828 nxt_int_t status)
829{
830 int rc;
831 VALUE headers;
832 nxt_ruby_headers_info_t headers_info;
833
834 headers = rb_ary_entry(result, 1);
835 if (nxt_slow_path(TYPE(headers) != T_HASH)) {
836 nxt_unit_req_error(req,
837 "Ruby: Invalid response 'headers' format from "
838 "application");
839
840 return NXT_UNIT_ERROR;
841 }
842
843 rc = NXT_UNIT_OK;
844
845 headers_info.rc = NXT_UNIT_OK;
846 headers_info.fields = 0;
847 headers_info.size = 0;
848 headers_info.req = req;
849
850 rb_hash_foreach(headers, nxt_ruby_hash_info,
851 (VALUE) (uintptr_t) &headers_info);
852 if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) {
853 return headers_info.rc;
854 }
855
856 rc = nxt_unit_response_init(req, status,
857 headers_info.fields, headers_info.size);
858 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
859 return rc;
860 }
861
862 rb_hash_foreach(headers, nxt_ruby_hash_add,
863 (VALUE) (uintptr_t) &headers_info);
864
865 return rc;
866}
867
868
869static int
870nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg)
871{
872 const char *value, *value_end, *pos;
873 nxt_ruby_headers_info_t *headers_info;
874
875 headers_info = (void *) (uintptr_t) arg;
876
877 if (nxt_slow_path(TYPE(r_key) != T_STRING)) {
878 nxt_unit_req_error(headers_info->req,
879 "Ruby: Wrong header entry 'key' from application");
880
881 goto fail;
882 }
883
884 if (nxt_slow_path(TYPE(r_value) != T_STRING)) {
885 nxt_unit_req_error(headers_info->req,
886 "Ruby: Wrong header entry 'value' from application");
887
888 goto fail;
889 }
890
891 value = RSTRING_PTR(r_value);
892 value_end = value + RSTRING_LEN(r_value);
893
894 pos = value;
895
896 for ( ;; ) {
897 pos = strchr(pos, '\n');
898
899 if (pos == NULL) {
900 break;
901 }
902
903 headers_info->fields++;
904 headers_info->size += RSTRING_LEN(r_key) + (pos - value);
905
906 pos++;
907 value = pos;
908 }
909
910 if (value <= value_end) {
911 headers_info->fields++;
912 headers_info->size += RSTRING_LEN(r_key) + (value_end - value);
913 }
914
915 return ST_CONTINUE;
916
917fail:
918
919 headers_info->rc = NXT_UNIT_ERROR;
920
921 return ST_STOP;
922}
923
924
925static int
926nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg)
927{
928 int *rc;
929 uint32_t key_len;
930 const char *value, *value_end, *pos;
931 nxt_ruby_headers_info_t *headers_info;
932
933 headers_info = (void *) (uintptr_t) arg;
934 rc = &headers_info->rc;
935
936 value = RSTRING_PTR(r_value);
937 value_end = value + RSTRING_LEN(r_value);
938
939 key_len = RSTRING_LEN(r_key);
940
941 pos = value;
942
943 for ( ;; ) {
944 pos = strchr(pos, '\n');
945
946 if (pos == NULL) {
947 break;
948 }
949
950 *rc = nxt_unit_response_add_field(headers_info->req,
951 RSTRING_PTR(r_key), key_len,
952 value, pos - value);
953 if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
954 goto fail;
955 }
956
957 pos++;
958 value = pos;
959 }
960
961 if (value <= value_end) {
962 *rc = nxt_unit_response_add_field(headers_info->req,
963 RSTRING_PTR(r_key), key_len,
964 value, value_end - value);
965 if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
966 goto fail;
967 }
968 }
969
970 return ST_CONTINUE;
971
972fail:
973
974 *rc = NXT_UNIT_ERROR;
975
976 return ST_STOP;
977}
978
979
980static int
981nxt_ruby_rack_result_body(nxt_unit_request_info_t *req, VALUE result)
982{
983 int rc;
984 VALUE fn, body;
985
986 body = rb_ary_entry(result, 2);
987
988 if (rb_respond_to(body, rb_intern("to_path"))) {
989
990 fn = rb_funcall(body, rb_intern("to_path"), 0);
991 if (nxt_slow_path(TYPE(fn) != T_STRING)) {
992 nxt_unit_req_error(req,
993 "Ruby: Failed to get 'body' file path from "
994 "application");
995
996 return NXT_UNIT_ERROR;
997 }
998
999 rc = nxt_ruby_rack_result_body_file_write(req, fn);
1000 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1001 return rc;
1002 }
1003
1004 } else if (rb_respond_to(body, rb_intern("each"))) {
1005 rb_block_call(body, rb_intern("each"), 0, 0,
1006 nxt_ruby_rack_result_body_each, (VALUE) (uintptr_t) req);
1007
1008 } else {
1009 nxt_unit_req_error(req,
1010 "Ruby: Invalid response 'body' format "
1011 "from application");
1012
1013 return NXT_UNIT_ERROR;
1014 }
1015
1016 if (rb_respond_to(body, rb_intern("close"))) {
1017 rb_funcall(body, rb_intern("close"), 0);
1018 }
1019
1020 return NXT_UNIT_OK;
1021}
1022
1023
1024typedef struct {
1025 int fd;
1026 off_t pos;
1027 off_t rest;
1028} nxt_ruby_rack_file_t;
1029
1030
1031static ssize_t
1032nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size)
1033{
1034 ssize_t res;
1035 nxt_ruby_rack_file_t *file;
1036
1037 file = read_info->data;
1038
1039 size = nxt_min(size, (size_t) file->rest);
1040
1041 res = pread(file->fd, dst, size, file->pos);
1042
1043 if (res >= 0) {
1044 file->pos += res;
1045 file->rest -= res;
1046
1047 if (size > (size_t) res) {
1048 file->rest = 0;
1049 }
1050 }
1051
1052 read_info->eof = file->rest == 0;
1053
1054 return res;
1055}
1056
1057
1058typedef struct {
1059 nxt_unit_read_info_t read_info;
1060 nxt_unit_request_info_t *req;
1061} nxt_ruby_read_info_t;
1062
1063
1064static int
1065nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req,
1066 VALUE filepath)
1067{
1068 int fd, rc;
1069 struct stat finfo;
1070 nxt_ruby_rack_file_t ruby_file;
1071 nxt_ruby_read_info_t ri;
1072
1073 fd = open(RSTRING_PTR(filepath), O_RDONLY, 0);
1074 if (nxt_slow_path(fd == -1)) {
1075 nxt_unit_req_error(req,
1076 "Ruby: Failed to open content file \"%s\": %s (%d)",
1077 RSTRING_PTR(filepath), strerror(errno), errno);
1078
1079 return NXT_UNIT_ERROR;
1080 }
1081
1082 rc = fstat(fd, &finfo);
1083 if (nxt_slow_path(rc == -1)) {
1084 nxt_unit_req_error(req,
1085 "Ruby: Content file fstat(\"%s\") failed: %s (%d)",
1086 RSTRING_PTR(filepath), strerror(errno), errno);
1087
1088 close(fd);
1089
1090 return NXT_UNIT_ERROR;
1091 }
1092
1093 ruby_file.fd = fd;
1094 ruby_file.pos = 0;
1095 ruby_file.rest = finfo.st_size;
1096
1097 ri.read_info.read = nxt_ruby_rack_file_read;
1098 ri.read_info.eof = ruby_file.rest == 0;
1099 ri.read_info.buf_size = ruby_file.rest;
1100 ri.read_info.data = &ruby_file;
1101 ri.req = req;
1102
1103 rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_response_write_cb,
1104 &ri,
1105 nxt_ruby_ubf,
1106 req->ctx);
1107
1108 close(fd);
1109
1110 return rc;
1111}
1112
1113
1114static void *
1115nxt_ruby_response_write_cb(void *data)
1116{
1117 int rc;
1118 nxt_ruby_read_info_t *ri;
1119
1120 ri = data;
1121
1122 rc = nxt_unit_response_write_cb(ri->req, &ri->read_info);
1123 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1124 nxt_unit_req_error(ri->req, "Ruby: Failed to write content file.");
1125 }
1126
1127 return (void *) (intptr_t) rc;
1128}
1129
1130
1131typedef struct {
1132 VALUE body;
1133 nxt_unit_request_info_t *req;
1134} nxt_ruby_write_info_t;
1135
1136
1137static VALUE
1138nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc,
1139 const VALUE *argv, VALUE blockarg)
1140{
1141 nxt_ruby_write_info_t wi;
1142
1143 if (TYPE(body) != T_STRING) {
1144 return Qnil;
1145 }
1146
1147 wi.body = body;
1148 wi.req = (void *) (uintptr_t) arg;
1149
1150 (void) rb_thread_call_without_gvl(nxt_ruby_response_write,
1151 (void *) (uintptr_t) &wi,
1152 nxt_ruby_ubf, wi.req->ctx);
1153
1154 return Qnil;
1155}
1156
1157
1158static void *
1159nxt_ruby_response_write(void *data)
1160{
1161 int rc;
1162 nxt_ruby_write_info_t *wi;
1163
1164 wi = data;
1165
1166 rc = nxt_unit_response_write(wi->req, RSTRING_PTR(wi->body),
1167 RSTRING_LEN(wi->body));
1168 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1169 nxt_unit_req_error(wi->req,
1170 "Ruby: Failed to write 'body' from application");
1171 }
1172
1173 return (void *) (intptr_t) rc;
1174}
1175
1176
1177static void
1178nxt_ruby_exception_log(nxt_unit_request_info_t *req, uint32_t level,
1179 const char *desc)
1180{
1181 int i;
1182 VALUE err, ary, eclass, msg;
1183
1184 nxt_unit_req_log(req, level, "Ruby: %s", desc);
1185
1186 err = rb_errinfo();
1187 if (nxt_slow_path(err == Qnil)) {
1188 return;
1189 }
1190
1191 eclass = rb_class_name(rb_class_of(err));
1192
1193 msg = rb_funcall(err, rb_intern("message"), 0);
1194 ary = rb_funcall(err, rb_intern("backtrace"), 0);
1195
1196 if (RARRAY_LEN(ary) == 0) {
1197 nxt_unit_req_log(req, level, "Ruby: %s (%s)", RSTRING_PTR(msg),
1198 RSTRING_PTR(eclass));
1199
1200 return;
1201 }
1202
1203 nxt_unit_req_log(req, level, "Ruby: %s: %s (%s)",
1204 RSTRING_PTR(RARRAY_PTR(ary)[0]),
1205 RSTRING_PTR(msg), RSTRING_PTR(eclass));
1206
1207 for (i = 1; i < RARRAY_LEN(ary); i++) {
1208 nxt_unit_req_log(req, level, "from %s",
1209 RSTRING_PTR(RARRAY_PTR(ary)[i]));
1210 }
1211}
1212
1213
1214static void
1215nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx)
1216{
1217 if (rctx->io_input != Qnil) {
1218 rb_gc_unregister_address(&rctx->io_input);
1219 }
1220
1221 if (rctx->io_error != Qnil) {
1222 rb_gc_unregister_address(&rctx->io_error);
1223 }
1224
1225 if (rctx->env != Qnil) {
1226 rb_gc_unregister_address(&rctx->env);
1227 }
1228}
1229
1230
1231static void
1232nxt_ruby_atexit(void)
1233{
1234 if (nxt_ruby_rackup != Qnil) {
1235 rb_gc_unregister_address(&nxt_ruby_rackup);
1236 }
1237
1238 if (nxt_ruby_call != Qnil) {
1239 rb_gc_unregister_address(&nxt_ruby_call);
1240 }
1241
1242 if (nxt_ruby_hook_procs != Qnil) {
1243 rb_gc_unregister_address(&nxt_ruby_hook_procs);
1244 }
1245
1246 nxt_ruby_done_strings();
1247
1248 ruby_cleanup(0);
1249}
1250
1251
1252static int
1253nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx)
1254{
1255 VALUE res;
1256 uint32_t i;
1257 nxt_ruby_ctx_t *rctx;
1258 nxt_ruby_app_conf_t *c;
1259
1261 /* Worker thread context. */
1262 if (!nxt_unit_is_main_ctx(ctx)) {
1263 return NXT_UNIT_OK;
1264 }
1265
1266 c = ctx->unit->data;
1267
1268 if (c->threads <= 1) {
1269 return NXT_UNIT_OK;
1270 }
1271
1272 for (i = 0; i < c->threads - 1; i++) {
1273 rctx = &nxt_ruby_ctxs[i];
1274
1275 rctx->ctx = ctx;
1276
1277 res = (VALUE) rb_thread_call_with_gvl(nxt_ruby_thread_create_gvl, rctx);
1278
1279 if (nxt_fast_path(res != Qnil)) {
1280 nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
1281
1282 rctx->thread = res;
1283
1284 } else {
1285 nxt_unit_alert(ctx, "thread #%d create failed", (int) (i + 1));
1286
1287 return NXT_UNIT_ERROR;
1288 }
1289 }
1290
1291 return NXT_UNIT_OK;
1292}
1293
1294
1295static void *
1296nxt_ruby_thread_create_gvl(void *rctx)
1297{
1298 VALUE res;
1299
1300 res = rb_thread_create(RUBY_METHOD_FUNC(nxt_ruby_thread_func), rctx);
1301
1302 return (void *) (uintptr_t) res;
1303}
1304
1305
1306static VALUE
1307nxt_ruby_thread_func(VALUE arg)
1308{
1309 int state;
1310 nxt_unit_ctx_t *ctx;
1311 nxt_ruby_ctx_t *rctx;
1312
1313 rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg;
1314
1315 nxt_unit_debug(rctx->ctx, "worker thread start");
1316
1317 ctx = nxt_unit_ctx_alloc(rctx->ctx, rctx);
1318 if (nxt_slow_path(ctx == NULL)) {
1319 goto fail;
1320 }
1321
1322 if (nxt_ruby_hook_procs != Qnil) {
1323 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
1324 if (nxt_slow_path(state != 0)) {
1325 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
1326 "Failed to call on_thread_boot()");
1327 }
1328 }
1329
1330 (void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx,
1331 nxt_ruby_ubf, ctx);
1332
1333 if (nxt_ruby_hook_procs != Qnil) {
1334 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
1335 if (nxt_slow_path(state != 0)) {
1336 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
1337 "Failed to call on_thread_shutdown()");
1338 }
1339 }
1340
1341 nxt_unit_done(ctx);
1342
1343fail:
1344
1345 nxt_unit_debug(NULL, "worker thread end");
1346
1347 return Qnil;
1348}
1349
1350
1351static void *
1352nxt_ruby_unit_run(void *ctx)
1353{
1354 return (void *) (intptr_t) nxt_unit_run(ctx);
1355}
1356
1357
1358static void
1359nxt_ruby_ubf(void *ctx)
1360{
1361 nxt_unit_warn(ctx, "Ruby: UBF");
1362}
1363
1364
1365static int
1366nxt_ruby_init_threads(nxt_ruby_app_conf_t *c)
1367{
1368 int state;
1369 uint32_t i;
1370 nxt_ruby_ctx_t *rctx;
1371
1372 if (c->threads <= 1) {
1373 return NXT_UNIT_OK;
1374 }
1375
1376 nxt_ruby_ctxs = nxt_unit_malloc(NULL, sizeof(nxt_ruby_ctx_t)
1377 * (c->threads - 1));
1378 if (nxt_slow_path(nxt_ruby_ctxs == NULL)) {
1379 nxt_unit_alert(NULL, "Failed to allocate run contexts array");
1380
1381 return NXT_UNIT_ERROR;
1382 }
1383
1384 for (i = 0; i < c->threads - 1; i++) {
1385 rctx = &nxt_ruby_ctxs[i];
1386
1387 rctx->env = Qnil;
1388 rctx->io_input = Qnil;
1389 rctx->io_error = Qnil;
1390 rctx->thread = Qnil;
1391 }
1392
1393 for (i = 0; i < c->threads - 1; i++) {
1394 rctx = &nxt_ruby_ctxs[i];
1395
1396 rctx->env = rb_protect(nxt_ruby_rack_env_create,
1397 (VALUE) (uintptr_t) rctx, &state);
1398 if (nxt_slow_path(rctx->env == Qnil || state != 0)) {
1399 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
1400 "Failed to create 'environ' variable");
1401 return NXT_UNIT_ERROR;
1402 }
1403 }
1404
1405 return NXT_UNIT_OK;
1406}
1407
1408
1409static void
1410nxt_ruby_join_threads(nxt_unit_ctx_t *ctx, nxt_ruby_app_conf_t *c)
1411{
1412 uint32_t i;
1413 nxt_ruby_ctx_t *rctx;
1414
1415 if (nxt_ruby_ctxs == NULL) {
1416 return;
1417 }
1418
1419 for (i = 0; i < c->threads - 1; i++) {
1420 rctx = &nxt_ruby_ctxs[i];
1421
1422 if (rctx->thread != Qnil) {
1423 rb_funcall(rctx->thread, rb_intern("join"), 0);
1424
1425 nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1));
1426
1427 } else {
1428 nxt_unit_debug(ctx, "thread #%d not started", (int) (i + 1));
1429 }
1430 }
1431
1432 for (i = 0; i < c->threads - 1; i++) {
1433 nxt_ruby_ctx_done(&nxt_ruby_ctxs[i]);
1434 }
1435
1436 nxt_unit_free(ctx, nxt_ruby_ctxs);
1437}
1260 c = ctx->unit->data;
1261
1262 if (c->threads <= 1) {
1263 return NXT_UNIT_OK;
1264 }
1265
1266 for (i = 0; i < c->threads - 1; i++) {
1267 rctx = &nxt_ruby_ctxs[i];
1268
1269 rctx->ctx = ctx;
1270
1271 res = (VALUE) rb_thread_call_with_gvl(nxt_ruby_thread_create_gvl, rctx);
1272
1273 if (nxt_fast_path(res != Qnil)) {
1274 nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
1275
1276 rctx->thread = res;
1277
1278 } else {
1279 nxt_unit_alert(ctx, "thread #%d create failed", (int) (i + 1));
1280
1281 return NXT_UNIT_ERROR;
1282 }
1283 }
1284
1285 return NXT_UNIT_OK;
1286}
1287
1288
1289static void *
1290nxt_ruby_thread_create_gvl(void *rctx)
1291{
1292 VALUE res;
1293
1294 res = rb_thread_create(RUBY_METHOD_FUNC(nxt_ruby_thread_func), rctx);
1295
1296 return (void *) (uintptr_t) res;
1297}
1298
1299
1300static VALUE
1301nxt_ruby_thread_func(VALUE arg)
1302{
1303 int state;
1304 nxt_unit_ctx_t *ctx;
1305 nxt_ruby_ctx_t *rctx;
1306
1307 rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg;
1308
1309 nxt_unit_debug(rctx->ctx, "worker thread start");
1310
1311 ctx = nxt_unit_ctx_alloc(rctx->ctx, rctx);
1312 if (nxt_slow_path(ctx == NULL)) {
1313 goto fail;
1314 }
1315
1316 if (nxt_ruby_hook_procs != Qnil) {
1317 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
1318 if (nxt_slow_path(state != 0)) {
1319 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
1320 "Failed to call on_thread_boot()");
1321 }
1322 }
1323
1324 (void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx,
1325 nxt_ruby_ubf, ctx);
1326
1327 if (nxt_ruby_hook_procs != Qnil) {
1328 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
1329 if (nxt_slow_path(state != 0)) {
1330 nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
1331 "Failed to call on_thread_shutdown()");
1332 }
1333 }
1334
1335 nxt_unit_done(ctx);
1336
1337fail:
1338
1339 nxt_unit_debug(NULL, "worker thread end");
1340
1341 return Qnil;
1342}
1343
1344
1345static void *
1346nxt_ruby_unit_run(void *ctx)
1347{
1348 return (void *) (intptr_t) nxt_unit_run(ctx);
1349}
1350
1351
1352static void
1353nxt_ruby_ubf(void *ctx)
1354{
1355 nxt_unit_warn(ctx, "Ruby: UBF");
1356}
1357
1358
1359static int
1360nxt_ruby_init_threads(nxt_ruby_app_conf_t *c)
1361{
1362 int state;
1363 uint32_t i;
1364 nxt_ruby_ctx_t *rctx;
1365
1366 if (c->threads <= 1) {
1367 return NXT_UNIT_OK;
1368 }
1369
1370 nxt_ruby_ctxs = nxt_unit_malloc(NULL, sizeof(nxt_ruby_ctx_t)
1371 * (c->threads - 1));
1372 if (nxt_slow_path(nxt_ruby_ctxs == NULL)) {
1373 nxt_unit_alert(NULL, "Failed to allocate run contexts array");
1374
1375 return NXT_UNIT_ERROR;
1376 }
1377
1378 for (i = 0; i < c->threads - 1; i++) {
1379 rctx = &nxt_ruby_ctxs[i];
1380
1381 rctx->env = Qnil;
1382 rctx->io_input = Qnil;
1383 rctx->io_error = Qnil;
1384 rctx->thread = Qnil;
1385 }
1386
1387 for (i = 0; i < c->threads - 1; i++) {
1388 rctx = &nxt_ruby_ctxs[i];
1389
1390 rctx->env = rb_protect(nxt_ruby_rack_env_create,
1391 (VALUE) (uintptr_t) rctx, &state);
1392 if (nxt_slow_path(rctx->env == Qnil || state != 0)) {
1393 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
1394 "Failed to create 'environ' variable");
1395 return NXT_UNIT_ERROR;
1396 }
1397 }
1398
1399 return NXT_UNIT_OK;
1400}
1401
1402
1403static void
1404nxt_ruby_join_threads(nxt_unit_ctx_t *ctx, nxt_ruby_app_conf_t *c)
1405{
1406 uint32_t i;
1407 nxt_ruby_ctx_t *rctx;
1408
1409 if (nxt_ruby_ctxs == NULL) {
1410 return;
1411 }
1412
1413 for (i = 0; i < c->threads - 1; i++) {
1414 rctx = &nxt_ruby_ctxs[i];
1415
1416 if (rctx->thread != Qnil) {
1417 rb_funcall(rctx->thread, rb_intern("join"), 0);
1418
1419 nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1));
1420
1421 } else {
1422 nxt_unit_debug(ctx, "thread #%d not started", (int) (i + 1));
1423 }
1424 }
1425
1426 for (i = 0; i < c->threads - 1; i++) {
1427 nxt_ruby_ctx_done(&nxt_ruby_ctxs[i]);
1428 }
1429
1430 nxt_unit_free(ctx, nxt_ruby_ctxs);
1431}