1 /* -*- c-basic-offset: 2 -*- */
2 /*
3 Copyright(C) 2012-2017 Brazil
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License version 2.1 as published by the Free Software Foundation.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
17 */
18
19 #ifndef WIN32
20 # define NGX_GRN_SUPPORT_STOP_BY_COMMAND
21 #endif
22
23 #include <ngx_config.h>
24 #include <ngx_core.h>
25 #include <ngx_http.h>
26
27 #include <groonga.h>
28 #include <groonga/plugin.h>
29
30 #include <sys/stat.h>
31
32 #ifdef NGX_GRN_SUPPORT_STOP_BY_COMMAND
33 # include <sys/types.h>
34 # include <unistd.h>
35 #endif
36
37 #define GRN_NO_FLAGS 0
38
39 typedef struct {
40 ngx_flag_t enabled;
41 ngx_str_t database_path;
42 char *database_path_cstr;
43 ngx_flag_t database_auto_create;
44 ngx_str_t base_path;
45 ngx_str_t log_path;
46 ngx_open_file_t *log_file;
47 grn_log_level log_level;
48 ngx_str_t query_log_path;
49 ngx_open_file_t *query_log_file;
50 size_t cache_limit;
51 ngx_msec_t default_request_timeout_msec;
52 char *config_file;
53 int config_line;
54 char *name;
55 grn_obj *database;
56 grn_cache *cache;
57 ngx_str_t cache_base_path;
58 } ngx_http_groonga_loc_conf_t;
59
60 typedef struct {
61 ngx_log_t *log;
62 ngx_pool_t *pool;
63 ngx_int_t rc;
64 } ngx_http_groonga_database_callback_data_t;
65
66 typedef struct {
67 grn_bool initialized;
68 grn_rc rc;
69 struct {
70 grn_bool processed;
71 grn_bool header_sent;
72 ngx_http_request_t *r;
73 ngx_int_t rc;
74 ngx_chain_t *free_chain;
75 ngx_chain_t *busy_chain;
76 } raw;
77 struct {
78 grn_obj head;
79 grn_obj body;
80 grn_obj foot;
81 } typed;
82 } ngx_http_groonga_handler_data_t;
83
84 typedef void (*ngx_http_groonga_loc_conf_callback_pt)(ngx_http_groonga_loc_conf_t *conf, void *user_data);
85
86 ngx_module_t ngx_http_groonga_module;
87
88 static grn_ctx ngx_http_groonga_context;
89 static grn_ctx *context = &ngx_http_groonga_context;
90 static ngx_http_groonga_loc_conf_t *ngx_http_groonga_current_location_conf = NULL;
91
92 static char *
ngx_str_null_terminate(ngx_pool_t * pool,const ngx_str_t * string)93 ngx_str_null_terminate(ngx_pool_t *pool, const ngx_str_t *string)
94 {
95 char *null_terminated_c_string;
96
97 null_terminated_c_string = ngx_pnalloc(pool, string->len + 1);
98 if (!null_terminated_c_string) {
99 return NULL;
100 }
101
102 memcpy(null_terminated_c_string, string->data, string->len);
103 null_terminated_c_string[string->len] = '\0';
104
105 return null_terminated_c_string;
106 }
107
108 static grn_bool
ngx_str_equal_c_string(ngx_str_t * string,const char * c_string)109 ngx_str_equal_c_string(ngx_str_t *string, const char *c_string)
110 {
111 if (string->len != strlen(c_string)) {
112 return GRN_FALSE;
113 }
114
115 return memcmp(c_string, string->data, string->len) == 0;
116 }
117
118 static grn_bool
ngx_str_is_custom_path(ngx_str_t * string)119 ngx_str_is_custom_path(ngx_str_t *string)
120 {
121 if (string->len == 0) {
122 return GRN_FALSE;
123 }
124
125 if (strncmp((const char *)(string->data), "off", string->len) == 0) {
126 return GRN_FALSE;
127 }
128
129 return GRN_TRUE;
130 }
131
132 static uint32_t
ngx_http_groonga_get_thread_limit(void * data)133 ngx_http_groonga_get_thread_limit(void *data)
134 {
135 return 1;
136 }
137
138 static ngx_int_t
ngx_http_groonga_grn_rc_to_http_status(grn_rc rc)139 ngx_http_groonga_grn_rc_to_http_status(grn_rc rc)
140 {
141 switch (rc) {
142 case GRN_SUCCESS :
143 return NGX_HTTP_OK;
144 case GRN_INVALID_ARGUMENT :
145 case GRN_FUNCTION_NOT_IMPLEMENTED :
146 case GRN_SYNTAX_ERROR :
147 return NGX_HTTP_BAD_REQUEST;
148 case GRN_CANCEL :
149 return NGX_HTTP_REQUEST_TIME_OUT;
150 default :
151 return NGX_HTTP_INTERNAL_SERVER_ERROR;
152 }
153 }
154
155 static void
ngx_http_groonga_write_fd(ngx_fd_t fd,u_char * buffer,size_t buffer_size,const char * message,size_t message_size)156 ngx_http_groonga_write_fd(ngx_fd_t fd,
157 u_char *buffer, size_t buffer_size,
158 const char *message, size_t message_size)
159 {
160 size_t rest_message_size = message_size;
161 const char *current_message = message;
162
163 while (rest_message_size > 0) {
164 size_t current_message_size;
165
166 if (rest_message_size > NGX_MAX_ERROR_STR) {
167 current_message_size = NGX_MAX_ERROR_STR;
168 } else {
169 current_message_size = rest_message_size;
170 }
171
172 grn_memcpy(buffer, current_message, current_message_size);
173 ngx_write_fd(fd, buffer, current_message_size);
174 rest_message_size -= current_message_size;
175 current_message += current_message_size;
176 }
177 }
178
179 static void
ngx_http_groonga_logger_log(grn_ctx * ctx,grn_log_level level,const char * timestamp,const char * title,const char * message,const char * location,void * user_data)180 ngx_http_groonga_logger_log(grn_ctx *ctx, grn_log_level level,
181 const char *timestamp, const char *title,
182 const char *message, const char *location,
183 void *user_data)
184 {
185 ngx_open_file_t *file = user_data;
186 char level_marks[] = " EACewnid-";
187 u_char buffer[NGX_MAX_ERROR_STR];
188
189 if (!file) {
190 return;
191 }
192
193 ngx_http_groonga_write_fd(file->fd,
194 buffer, NGX_MAX_ERROR_STR,
195 timestamp, strlen(timestamp));
196 ngx_write_fd(file->fd, "|", 1);
197 ngx_write_fd(file->fd, level_marks + level, 1);
198 ngx_write_fd(file->fd, "|", 1);
199 if (location && *location) {
200 ngx_http_groonga_write_fd(file->fd,
201 buffer, NGX_MAX_ERROR_STR,
202 location, strlen(location));
203 ngx_write_fd(file->fd, ": ", 2);
204 if (title && *title) {
205 ngx_http_groonga_write_fd(file->fd,
206 buffer, NGX_MAX_ERROR_STR,
207 title, strlen(title));
208 ngx_write_fd(file->fd, " ", 1);
209 }
210 } else {
211 ngx_http_groonga_write_fd(file->fd,
212 buffer, NGX_MAX_ERROR_STR,
213 title, strlen(title));
214 ngx_write_fd(file->fd, " ", 1);
215 }
216 ngx_http_groonga_write_fd(file->fd,
217 buffer, NGX_MAX_ERROR_STR,
218 message, strlen(message));
219 ngx_write_fd(file->fd, "\n", 1);
220 }
221
222 static void
ngx_http_groonga_logger_reopen(grn_ctx * ctx,void * user_data)223 ngx_http_groonga_logger_reopen(grn_ctx *ctx, void *user_data)
224 {
225 GRN_LOG(ctx, GRN_LOG_NOTICE, "log will be closed.");
226 ngx_reopen_files((ngx_cycle_t *)ngx_cycle, -1);
227 GRN_LOG(ctx, GRN_LOG_NOTICE, "log opened.");
228 }
229
230 static void
ngx_http_groonga_logger_fin(grn_ctx * ctx,void * user_data)231 ngx_http_groonga_logger_fin(grn_ctx *ctx, void *user_data)
232 {
233 }
234
235 static grn_logger ngx_http_groonga_logger = {
236 GRN_LOG_DEFAULT_LEVEL,
237 GRN_LOG_TIME | GRN_LOG_MESSAGE | GRN_LOG_PID,
238 NULL,
239 ngx_http_groonga_logger_log,
240 ngx_http_groonga_logger_reopen,
241 ngx_http_groonga_logger_fin
242 };
243
244 static ngx_int_t
ngx_http_groonga_context_init_logger(ngx_http_groonga_loc_conf_t * location_conf,ngx_pool_t * pool,ngx_log_t * log)245 ngx_http_groonga_context_init_logger(ngx_http_groonga_loc_conf_t *location_conf,
246 ngx_pool_t *pool,
247 ngx_log_t *log)
248 {
249 if (ngx_http_groonga_current_location_conf) {
250 ngx_http_groonga_current_location_conf->log_level =
251 grn_logger_get_max_level(context);
252 }
253
254 ngx_http_groonga_logger.max_level = location_conf->log_level;
255 ngx_http_groonga_logger.user_data = location_conf->log_file;
256 grn_logger_set(context, &ngx_http_groonga_logger);
257
258 return NGX_OK;
259 }
260
261 static void
ngx_http_groonga_query_logger_log(grn_ctx * ctx,unsigned int flag,const char * timestamp,const char * info,const char * message,void * user_data)262 ngx_http_groonga_query_logger_log(grn_ctx *ctx, unsigned int flag,
263 const char *timestamp, const char *info,
264 const char *message, void *user_data)
265 {
266 ngx_open_file_t *file = user_data;
267 u_char buffer[NGX_MAX_ERROR_STR];
268 u_char *last;
269
270 if (!file) {
271 return;
272 }
273
274 last = ngx_slprintf(buffer, buffer + NGX_MAX_ERROR_STR,
275 "%s|%s%s\n",
276 timestamp, info, message);
277 ngx_write_fd(file->fd, buffer, last - buffer);
278 }
279
280 static void
ngx_http_groonga_query_logger_reopen(grn_ctx * ctx,void * user_data)281 ngx_http_groonga_query_logger_reopen(grn_ctx *ctx, void *user_data)
282 {
283 ngx_reopen_files((ngx_cycle_t *)ngx_cycle, -1);
284 }
285
286 static void
ngx_http_groonga_query_logger_fin(grn_ctx * ctx,void * user_data)287 ngx_http_groonga_query_logger_fin(grn_ctx *ctx, void *user_data)
288 {
289 }
290
291 static grn_query_logger ngx_http_groonga_query_logger = {
292 GRN_QUERY_LOG_DEFAULT,
293 NULL,
294 ngx_http_groonga_query_logger_log,
295 ngx_http_groonga_query_logger_reopen,
296 ngx_http_groonga_query_logger_fin
297 };
298
299 static ngx_int_t
ngx_http_groonga_context_init_query_logger(ngx_http_groonga_loc_conf_t * location_conf,ngx_pool_t * pool,ngx_log_t * log)300 ngx_http_groonga_context_init_query_logger(ngx_http_groonga_loc_conf_t *location_conf,
301 ngx_pool_t *pool,
302 ngx_log_t *log)
303 {
304 ngx_http_groonga_query_logger.user_data = location_conf->query_log_file;
305 grn_query_logger_set(context, &ngx_http_groonga_query_logger);
306
307 return NGX_OK;
308 }
309
310 static ngx_int_t
ngx_http_groonga_context_init(ngx_http_groonga_loc_conf_t * location_conf,ngx_pool_t * pool,ngx_log_t * log)311 ngx_http_groonga_context_init(ngx_http_groonga_loc_conf_t *location_conf,
312 ngx_pool_t *pool,
313 ngx_log_t *log)
314 {
315 ngx_int_t status;
316
317 if (location_conf == ngx_http_groonga_current_location_conf) {
318 return NGX_OK;
319 }
320
321 status = ngx_http_groonga_context_init_logger(location_conf,
322 pool,
323 log);
324 if (status == NGX_ERROR) {
325 return status;
326 }
327
328 status = ngx_http_groonga_context_init_query_logger(location_conf,
329 pool,
330 log);
331 if (status == NGX_ERROR) {
332 return status;
333 }
334
335 grn_ctx_use(context, location_conf->database);
336 grn_cache_current_set(context, location_conf->cache);
337
338 /* TODO: It doesn't work yet. We need to implement request timeout
339 * handler. */
340 if (location_conf->default_request_timeout_msec == NGX_CONF_UNSET_MSEC) {
341 grn_set_default_request_timeout(0.0);
342 } else {
343 double timeout;
344 timeout = location_conf->default_request_timeout_msec / 1000.0;
345 grn_set_default_request_timeout(timeout);
346 }
347
348 ngx_http_groonga_current_location_conf = location_conf;
349
350 return status;
351 }
352
353 static void
ngx_http_groonga_context_log_error(ngx_log_t * log)354 ngx_http_groonga_context_log_error(ngx_log_t *log)
355 {
356 if (context->rc == GRN_SUCCESS) {
357 return;
358 }
359
360 ngx_log_error(NGX_LOG_ERR, log, 0, "%s", context->errbuf);
361 }
362
363 static ngx_int_t
ngx_http_groonga_context_check_error(ngx_log_t * log)364 ngx_http_groonga_context_check_error(ngx_log_t *log)
365 {
366 if (context->rc == GRN_SUCCESS) {
367 return NGX_OK;
368 } else {
369 ngx_http_groonga_context_log_error(log);
370 return NGX_HTTP_BAD_REQUEST;
371 }
372 }
373
374 static ngx_buf_t *
ngx_http_groonga_grn_obj_to_ngx_buf(ngx_pool_t * pool,grn_obj * object)375 ngx_http_groonga_grn_obj_to_ngx_buf(ngx_pool_t *pool, grn_obj *object)
376 {
377 ngx_buf_t *buffer;
378 buffer = ngx_pcalloc(pool, sizeof(ngx_buf_t));
379 if (buffer == NULL) {
380 return NULL;
381 }
382
383 /* adjust the pointers of the buffer */
384 buffer->pos = (u_char *)GRN_TEXT_VALUE(object);
385 buffer->last = (u_char *)GRN_TEXT_VALUE(object) + GRN_TEXT_LEN(object);
386 buffer->memory = 1; /* this buffer is in memory */
387 buffer->in_file = 0;
388
389 return buffer;
390 }
391
392 static void
ngx_http_groonga_handler_cleanup(void * user_data)393 ngx_http_groonga_handler_cleanup(void *user_data)
394 {
395 ngx_http_groonga_handler_data_t *data = user_data;
396
397 if (!data->initialized) {
398 return;
399 }
400
401 GRN_OBJ_FIN(context, &(data->typed.head));
402 GRN_OBJ_FIN(context, &(data->typed.body));
403 GRN_OBJ_FIN(context, &(data->typed.foot));
404 }
405
406 static void
ngx_http_groonga_handler_set_content_type(ngx_http_request_t * r,const char * content_type)407 ngx_http_groonga_handler_set_content_type(ngx_http_request_t *r,
408 const char *content_type)
409 {
410 r->headers_out.content_type.len = strlen(content_type);
411 r->headers_out.content_type.data = (u_char *)content_type;
412 r->headers_out.content_type_len = r->headers_out.content_type.len;
413 }
414
415 static void
ngx_http_groonga_context_receive_handler_raw(grn_ctx * context,int flags,ngx_http_groonga_handler_data_t * data)416 ngx_http_groonga_context_receive_handler_raw(grn_ctx *context,
417 int flags,
418 ngx_http_groonga_handler_data_t *data)
419 {
420 char *chunk = NULL;
421 unsigned int chunk_size = 0;
422 int recv_flags;
423 ngx_http_request_t *r;
424 ngx_log_t *log;
425 grn_bool is_last_chunk;
426
427 grn_ctx_recv(context, &chunk, &chunk_size, &recv_flags);
428 data->raw.processed = GRN_TRUE;
429
430 if (data->raw.rc != NGX_OK) {
431 return;
432 }
433
434 r = data->raw.r;
435 log = r->connection->log;
436 is_last_chunk = (flags & GRN_CTX_TAIL);
437
438 if (!data->raw.header_sent) {
439 ngx_http_groonga_handler_set_content_type(r, grn_ctx_get_mime_type(context));
440 r->headers_out.status = NGX_HTTP_OK;
441 if (is_last_chunk) {
442 r->headers_out.content_length_n = chunk_size;
443 if (chunk_size == 0) {
444 r->header_only = 1;
445 }
446 } else {
447 r->headers_out.content_length_n = -1;
448 }
449 data->raw.rc = ngx_http_send_header(r);
450 data->raw.header_sent = GRN_TRUE;
451
452 if (data->raw.rc != NGX_OK) {
453 return;
454 }
455 }
456
457 if (chunk_size > 0 || is_last_chunk) {
458 ngx_chain_t *chain;
459
460 chain = ngx_chain_get_free_buf(r->pool, &(data->raw.free_chain));
461 if (!chain) {
462 ngx_log_error(NGX_LOG_ERR, log, 0,
463 "http_groonga: failed to allocate memory for chunked body");
464 data->raw.rc = NGX_ERROR;
465 return;
466 }
467 if (chunk_size == 0) {
468 chain->buf->pos = NULL;
469 chain->buf->last = NULL;
470 chain->buf->memory = 0;
471 } else {
472 chain->buf->pos = (u_char *)chunk;
473 chain->buf->last = (u_char *)chunk + chunk_size;
474 chain->buf->memory = 1;
475 }
476 chain->buf->tag = (ngx_buf_tag_t)&ngx_http_groonga_module;
477 chain->buf->flush = 1;
478 chain->buf->temporary = 0;
479 chain->buf->in_file = 0;
480 if (is_last_chunk) {
481 chain->buf->last_buf = 1;
482 } else {
483 chain->buf->last_buf = 0;
484 }
485 chain->next = NULL;
486
487 data->raw.rc = ngx_http_output_filter(r, chain);
488 ngx_chain_update_chains(r->pool,
489 &(data->raw.free_chain),
490 &(data->raw.busy_chain),
491 &chain,
492 (ngx_buf_tag_t)&ngx_http_groonga_module);
493 }
494 }
495
496 static void
ngx_http_groonga_context_receive_handler_typed(grn_ctx * context,int flags,ngx_http_groonga_handler_data_t * data)497 ngx_http_groonga_context_receive_handler_typed(grn_ctx *context,
498 int flags,
499 ngx_http_groonga_handler_data_t *data)
500 {
501 char *result = NULL;
502 unsigned int result_size = 0;
503 int recv_flags;
504
505 if (!(flags & GRN_CTX_TAIL)) {
506 return;
507 }
508
509 grn_ctx_recv(context, &result, &result_size, &recv_flags);
510
511 #ifdef NGX_GRN_SUPPORT_STOP_BY_COMMAND
512 if (recv_flags == GRN_CTX_QUIT) {
513 ngx_int_t ngx_rc;
514 ngx_int_t ngx_pid;
515
516 if (ngx_process == NGX_PROCESS_SINGLE) {
517 ngx_pid = getpid();
518 } else {
519 ngx_pid = getppid();
520 }
521
522 ngx_rc = ngx_os_signal_process((ngx_cycle_t *)ngx_cycle,
523 "quit",
524 ngx_pid);
525 if (ngx_rc == NGX_OK) {
526 context->stat &= ~GRN_CTX_QUIT;
527 grn_ctx_recv(context, &result, &result_size, &recv_flags);
528 context->stat |= GRN_CTX_QUIT;
529 } else {
530 context->rc = GRN_OPERATION_NOT_PERMITTED;
531 result = "false";
532 result_size = strlen(result);
533 context->stat &= ~GRN_CTX_QUIT;
534 }
535 }
536 #endif
537
538 if (result_size > 0 ||
539 GRN_TEXT_LEN(&(data->typed.body)) > 0 ||
540 context->rc != GRN_SUCCESS) {
541 if (result_size > 0) {
542 GRN_TEXT_PUT(context, &(data->typed.body), result, result_size);
543 }
544
545 grn_output_envelope(context,
546 context->rc,
547 &(data->typed.head),
548 &(data->typed.body),
549 &(data->typed.foot),
550 NULL,
551 0);
552 }
553 }
554
555 static void
ngx_http_groonga_context_receive_handler(grn_ctx * context,int flags,void * callback_data)556 ngx_http_groonga_context_receive_handler(grn_ctx *context,
557 int flags,
558 void *callback_data)
559 {
560 ngx_http_groonga_handler_data_t *data = callback_data;
561
562 switch (grn_ctx_get_output_type(context)) {
563 case GRN_CONTENT_GROONGA_COMMAND_LIST :
564 case GRN_CONTENT_NONE :
565 ngx_http_groonga_context_receive_handler_raw(context, flags, data);
566 break;
567 default :
568 ngx_http_groonga_context_receive_handler_typed(context, flags, data);
569 break;
570 }
571 }
572
573 static ngx_int_t
ngx_http_groonga_extract_command_path(ngx_http_request_t * r,ngx_str_t * command_path)574 ngx_http_groonga_extract_command_path(ngx_http_request_t *r,
575 ngx_str_t *command_path)
576 {
577 size_t base_path_length;
578
579 ngx_http_core_loc_conf_t *http_location_conf;
580 ngx_http_groonga_loc_conf_t *location_conf;
581
582 http_location_conf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
583 location_conf = ngx_http_get_module_loc_conf(r, ngx_http_groonga_module);
584
585 command_path->data = r->unparsed_uri.data;
586 command_path->len = r->unparsed_uri.len;
587 base_path_length = http_location_conf->name.len;
588 if (location_conf->base_path.len > 0) {
589 if (command_path->len < location_conf->base_path.len) {
590 ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
591 "requested URI is shorter than groonga_base_path: "
592 "URI: <%V>, groonga_base_path: <%V>",
593 &(r->unparsed_uri), &(location_conf->base_path));
594 } else if (strncmp((const char *)command_path->data,
595 (const char *)(location_conf->base_path.data),
596 location_conf->base_path.len) < 0) {
597 ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
598 "groonga_base_path doesn't match requested URI: "
599 "URI: <%V>, groonga_base_path: <%V>",
600 &(r->unparsed_uri), &(location_conf->base_path));
601 } else {
602 base_path_length = location_conf->base_path.len;
603 }
604 }
605 command_path->data += base_path_length;
606 command_path->len -= base_path_length;
607 if (command_path->len > 0 && command_path->data[0] == '/') {
608 command_path->data += 1;
609 command_path->len -= 1;
610 }
611 if (command_path->len == 0) {
612 return NGX_HTTP_BAD_REQUEST;
613 }
614
615 return NGX_OK;
616 }
617
618 static ngx_int_t
ngx_http_groonga_handler_create_data(ngx_http_request_t * r,ngx_http_groonga_handler_data_t ** data_return)619 ngx_http_groonga_handler_create_data(ngx_http_request_t *r,
620 ngx_http_groonga_handler_data_t **data_return)
621 {
622 ngx_int_t rc;
623
624 ngx_http_groonga_loc_conf_t *location_conf;
625
626 ngx_http_cleanup_t *cleanup;
627 ngx_http_groonga_handler_data_t *data;
628
629 location_conf = ngx_http_get_module_loc_conf(r, ngx_http_groonga_module);
630
631 rc = ngx_http_groonga_context_init(location_conf, r->pool, r->connection->log);
632 if (rc != NGX_OK) {
633 return rc;
634 }
635
636 cleanup = ngx_http_cleanup_add(r, sizeof(ngx_http_groonga_handler_data_t));
637 cleanup->handler = ngx_http_groonga_handler_cleanup;
638 data = cleanup->data;
639 *data_return = data;
640
641 data->initialized = GRN_TRUE;
642 data->rc = GRN_SUCCESS;
643
644 data->raw.processed = GRN_FALSE;
645 data->raw.header_sent = GRN_FALSE;
646 data->raw.r = r;
647 data->raw.rc = NGX_OK;
648 data->raw.free_chain = NULL;
649 data->raw.busy_chain = NULL;
650
651 GRN_TEXT_INIT(&(data->typed.head), GRN_NO_FLAGS);
652 GRN_TEXT_INIT(&(data->typed.body), GRN_NO_FLAGS);
653 GRN_TEXT_INIT(&(data->typed.foot), GRN_NO_FLAGS);
654
655 grn_ctx_use(context, location_conf->database);
656 rc = ngx_http_groonga_context_check_error(r->connection->log);
657 if (rc != NGX_OK) {
658 return rc;
659 }
660
661 grn_ctx_recv_handler_set(context,
662 ngx_http_groonga_context_receive_handler,
663 data);
664
665 return NGX_OK;
666 }
667
668 static void
ngx_http_groonga_handler_process_command_path(ngx_http_request_t * r,ngx_str_t * command_path,ngx_http_groonga_handler_data_t * data,int flags)669 ngx_http_groonga_handler_process_command_path(ngx_http_request_t *r,
670 ngx_str_t *command_path,
671 ngx_http_groonga_handler_data_t *data,
672 int flags)
673 {
674 grn_obj uri;
675
676 GRN_TEXT_INIT(&uri, 0);
677 GRN_TEXT_PUTS(context, &uri, "/d/");
678 GRN_TEXT_PUT(context, &uri, command_path->data, command_path->len);
679 grn_ctx_send(context, GRN_TEXT_VALUE(&uri), GRN_TEXT_LEN(&uri), flags);
680 data->rc = context->rc;
681 ngx_http_groonga_context_log_error(r->connection->log);
682 GRN_OBJ_FIN(context, &uri);
683 }
684
685 static grn_bool
ngx_http_groonga_handler_validate_post_command(ngx_http_request_t * r,ngx_str_t * command_path,ngx_http_groonga_handler_data_t * data)686 ngx_http_groonga_handler_validate_post_command(ngx_http_request_t *r,
687 ngx_str_t *command_path,
688 ngx_http_groonga_handler_data_t *data)
689 {
690 ngx_str_t command;
691
692 command.data = command_path->data;
693 if (r->args.len == 0) {
694 command.len = command_path->len;
695 } else {
696 command.len = command_path->len - r->args.len - strlen("?");
697 }
698 if (ngx_str_equal_c_string(&command, "load")) {
699 return GRN_TRUE;
700 }
701
702 data->rc = GRN_INVALID_ARGUMENT;
703 ngx_http_groonga_handler_set_content_type(r, "text/plain");
704 GRN_TEXT_PUTS(context, &(data->typed.body),
705 "command for POST must be <load>: <");
706 GRN_TEXT_PUT(context, &(data->typed.body), command.data, command.len);
707 GRN_TEXT_PUTS(context, &(data->typed.body), ">");
708
709 return GRN_FALSE;
710 }
711
712 static void
ngx_http_groonga_send_body(ngx_http_request_t * r,ngx_http_groonga_handler_data_t * data)713 ngx_http_groonga_send_body(ngx_http_request_t *r,
714 ngx_http_groonga_handler_data_t *data)
715 {
716 ngx_log_t *log;
717 grn_obj line_buffer;
718 size_t line_start_offset;
719 size_t line_check_start_offset;
720 ngx_chain_t *chain;
721 size_t line_buffer_chunk_size = 4096;
722
723 log = r->connection->log;
724
725 GRN_TEXT_INIT(&line_buffer, 0);
726 line_start_offset = 0;
727 line_check_start_offset = 0;
728 for (chain = r->request_body->bufs; chain; chain = chain->next) {
729 ngx_buf_t *buffer;
730 size_t rest_buffer_size;
731 off_t offset;
732
733 buffer = chain->buf;
734 rest_buffer_size = ngx_buf_size(buffer);
735 offset = 0;
736 while (rest_buffer_size > 0) {
737 size_t current_buffer_size;
738
739 if (rest_buffer_size > line_buffer_chunk_size) {
740 current_buffer_size = line_buffer_chunk_size;
741 } else {
742 current_buffer_size = rest_buffer_size;
743 }
744
745 if (ngx_buf_in_memory(buffer)) {
746 GRN_TEXT_PUT(context,
747 &line_buffer,
748 buffer->pos + offset,
749 current_buffer_size);
750 } else {
751 ngx_int_t rc;
752 grn_bulk_reserve(context, &line_buffer, current_buffer_size);
753 rc = ngx_read_file(buffer->file,
754 (u_char *)GRN_BULK_CURR(&line_buffer),
755 current_buffer_size,
756 offset);
757 if (rc < 0) {
758 GRN_PLUGIN_ERROR(context,
759 GRN_INPUT_OUTPUT_ERROR,
760 "[nginx][post][body][read] "
761 "failed to read a request body from file");
762 goto exit;
763 }
764 GRN_BULK_INCR_LEN(&line_buffer, current_buffer_size);
765 }
766 offset += current_buffer_size;
767 rest_buffer_size -= current_buffer_size;
768
769 {
770 const char *line_start;
771 const char *line_current;
772 const char *line_end;
773
774 line_start = GRN_TEXT_VALUE(&line_buffer) + line_start_offset;
775 line_end = GRN_TEXT_VALUE(&line_buffer) + GRN_TEXT_LEN(&line_buffer);
776 for (line_current = line_start + line_check_start_offset;
777 line_current < line_end;
778 line_current++) {
779 size_t line_length;
780 int flags = GRN_NO_FLAGS;
781
782 if (*line_current != '\n') {
783 continue;
784 }
785
786 line_length = line_current - line_start + 1;
787 if (line_current + 1 == line_end &&
788 !chain->next &&
789 rest_buffer_size == 0) {
790 flags |= GRN_CTX_TAIL;
791 }
792 grn_ctx_send(context, line_start, line_length, flags);
793 line_start_offset += line_length;
794 line_start += line_length;
795 ngx_http_groonga_context_log_error(log);
796 if (context->rc != GRN_SUCCESS && data->rc == GRN_SUCCESS) {
797 data->rc = context->rc;
798 }
799 }
800
801 if (line_start_offset == 0) {
802 line_buffer_chunk_size *= 2;
803 line_check_start_offset = GRN_TEXT_LEN(&line_buffer);
804 } else if ((size_t)GRN_TEXT_LEN(&line_buffer) == line_start_offset) {
805 GRN_BULK_REWIND(&line_buffer);
806 line_start_offset = 0;
807 line_check_start_offset = 0;
808 } else {
809 size_t rest_line_size;
810 rest_line_size = GRN_TEXT_LEN(&line_buffer) - line_start_offset;
811 grn_memmove(GRN_TEXT_VALUE(&line_buffer),
812 GRN_TEXT_VALUE(&line_buffer) + line_start_offset,
813 rest_line_size);
814 grn_bulk_truncate(context, &line_buffer, rest_line_size);
815 line_start_offset = 0;
816 line_check_start_offset = GRN_TEXT_LEN(&line_buffer);
817 }
818 }
819 }
820 }
821
822 if (GRN_TEXT_LEN(&line_buffer) > 0) {
823 grn_ctx_send(context,
824 GRN_TEXT_VALUE(&line_buffer),
825 GRN_TEXT_LEN(&line_buffer),
826 GRN_CTX_TAIL);
827 ngx_http_groonga_context_log_error(log);
828 if (context->rc != GRN_SUCCESS && data->rc == GRN_SUCCESS) {
829 data->rc = context->rc;
830 }
831 }
832
833 exit :
834 GRN_OBJ_FIN(context, &line_buffer);
835 }
836
837 static void
ngx_http_groonga_handler_process_body(ngx_http_request_t * r,ngx_http_groonga_handler_data_t * data)838 ngx_http_groonga_handler_process_body(ngx_http_request_t *r,
839 ngx_http_groonga_handler_data_t *data)
840 {
841 ngx_buf_t *body;
842
843 body = r->request_body->bufs->buf;
844 if (!body) {
845 data->rc = GRN_INVALID_ARGUMENT;
846 ngx_http_groonga_handler_set_content_type(r, "text/plain");
847 GRN_TEXT_PUTS(context, &(data->typed.body), "must send load data as body");
848 return;
849 }
850
851 ngx_http_groonga_send_body(r, data);
852 }
853
854
855 static void
ngx_http_groonga_handler_process_load(ngx_http_request_t * r,ngx_str_t * command_path,ngx_http_groonga_handler_data_t * data)856 ngx_http_groonga_handler_process_load(ngx_http_request_t *r,
857 ngx_str_t *command_path,
858 ngx_http_groonga_handler_data_t *data)
859 {
860 if (!ngx_http_groonga_handler_validate_post_command(r, command_path, data)) {
861 return;
862 }
863
864 ngx_http_groonga_handler_process_command_path(r,
865 command_path,
866 data,
867 GRN_NO_FLAGS);
868 if (data->rc != GRN_SUCCESS) {
869 return;
870 }
871
872 ngx_http_groonga_handler_process_body(r, data);
873 }
874
875 static ngx_chain_t *
ngx_http_groonga_attach_chain(ngx_chain_t * chain,ngx_chain_t * new_chain)876 ngx_http_groonga_attach_chain(ngx_chain_t *chain, ngx_chain_t *new_chain)
877 {
878 ngx_chain_t *last_chain;
879
880 if (new_chain->buf->last == new_chain->buf->pos) {
881 return chain;
882 }
883
884 new_chain->buf->last_buf = 1;
885 new_chain->next = NULL;
886 if (!chain) {
887 return new_chain;
888 }
889
890 chain->buf->last_buf = 0;
891 last_chain = chain;
892 while (last_chain->next) {
893 last_chain = last_chain->next;
894 }
895 last_chain->next = new_chain;
896 return chain;
897 }
898
899 static ngx_int_t
ngx_http_groonga_handler_send_response(ngx_http_request_t * r,ngx_http_groonga_handler_data_t * data)900 ngx_http_groonga_handler_send_response(ngx_http_request_t *r,
901 ngx_http_groonga_handler_data_t *data)
902 {
903 ngx_int_t rc;
904 const char *content_type;
905 ngx_buf_t *head_buf, *body_buf, *foot_buf;
906 ngx_chain_t head_chain, body_chain, foot_chain;
907 ngx_chain_t *output_chain = NULL;
908
909 if (data->raw.processed) {
910 return data->raw.rc;
911 }
912
913 /* set the 'Content-type' header */
914 if (r->headers_out.content_type.len == 0) {
915 grn_obj *foot = &(data->typed.foot);
916 if (grn_ctx_get_output_type(context) == GRN_CONTENT_JSON &&
917 GRN_TEXT_LEN(foot) > 0 &&
918 GRN_TEXT_VALUE(foot)[GRN_TEXT_LEN(foot) - 1] == ';') {
919 content_type = "application/javascript";
920 } else {
921 content_type = grn_ctx_get_mime_type(context);
922 }
923 ngx_http_groonga_handler_set_content_type(r, content_type);
924 }
925
926 /* allocate buffers for a response body */
927 head_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->typed.head));
928 if (!head_buf) {
929 return NGX_HTTP_INTERNAL_SERVER_ERROR;
930 }
931
932 body_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->typed.body));
933 if (!body_buf) {
934 return NGX_HTTP_INTERNAL_SERVER_ERROR;
935 }
936
937 foot_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->typed.foot));
938 if (!foot_buf) {
939 return NGX_HTTP_INTERNAL_SERVER_ERROR;
940 }
941
942 /* attach buffers to the buffer chain */
943 head_chain.buf = head_buf;
944 output_chain = ngx_http_groonga_attach_chain(output_chain, &head_chain);
945 body_chain.buf = body_buf;
946 output_chain = ngx_http_groonga_attach_chain(output_chain, &body_chain);
947 foot_chain.buf = foot_buf;
948 output_chain = ngx_http_groonga_attach_chain(output_chain, &foot_chain);
949
950 /* set the status line */
951 r->headers_out.status = ngx_http_groonga_grn_rc_to_http_status(data->rc);
952 r->headers_out.content_length_n = GRN_TEXT_LEN(&(data->typed.head)) +
953 GRN_TEXT_LEN(&(data->typed.body)) +
954 GRN_TEXT_LEN(&(data->typed.foot));
955 if (r->headers_out.content_length_n == 0) {
956 r->header_only = 1;
957 }
958
959 /* send the headers of your response */
960 rc = ngx_http_send_header(r);
961
962 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
963 return rc;
964 }
965
966 /* send the buffer chain of your response */
967 rc = ngx_http_output_filter(r, output_chain);
968
969 return rc;
970 }
971
972 static ngx_int_t
ngx_http_groonga_handler_get(ngx_http_request_t * r)973 ngx_http_groonga_handler_get(ngx_http_request_t *r)
974 {
975 ngx_int_t rc;
976 ngx_str_t command_path;
977 ngx_http_groonga_handler_data_t *data;
978
979 rc = ngx_http_groonga_extract_command_path(r, &command_path);
980 if (rc != NGX_OK) {
981 return rc;
982 }
983
984 rc = ngx_http_groonga_handler_create_data(r, &data);
985 if (rc != NGX_OK) {
986 return rc;
987 }
988
989 ngx_http_groonga_handler_process_command_path(r,
990 &command_path,
991 data,
992 GRN_CTX_TAIL);
993
994 /* discard request body, since we don't need it here */
995 rc = ngx_http_discard_request_body(r);
996 if (rc != NGX_OK) {
997 return rc;
998 }
999
1000 rc = ngx_http_groonga_handler_send_response(r, data);
1001
1002 return rc;
1003 }
1004
1005 static void
ngx_http_groonga_handler_post_send_error_response(ngx_http_request_t * r,ngx_int_t rc)1006 ngx_http_groonga_handler_post_send_error_response(ngx_http_request_t *r,
1007 ngx_int_t rc)
1008 {
1009 r->headers_out.status = rc;
1010 r->headers_out.content_length_n = 0;
1011 r->header_only = 1;
1012 rc = ngx_http_send_header(r);
1013 ngx_http_finalize_request(r, rc);
1014 }
1015
1016 static void
ngx_http_groonga_handler_post(ngx_http_request_t * r)1017 ngx_http_groonga_handler_post(ngx_http_request_t *r)
1018 {
1019 ngx_int_t rc;
1020 ngx_str_t command_path;
1021 ngx_http_groonga_handler_data_t *data = NULL;
1022
1023 rc = ngx_http_groonga_extract_command_path(r, &command_path);
1024 if (rc != NGX_OK) {
1025 ngx_http_groonga_handler_post_send_error_response(r, rc);
1026 return;
1027 }
1028
1029 rc = ngx_http_groonga_handler_create_data(r, &data);
1030 if (rc != NGX_OK) {
1031 ngx_http_groonga_handler_post_send_error_response(r, rc);
1032 return;
1033 }
1034
1035 ngx_http_groonga_handler_process_load(r, &command_path, data);
1036 rc = ngx_http_groonga_handler_send_response(r, data);
1037 ngx_http_finalize_request(r, rc);
1038 }
1039
1040 static ngx_int_t
ngx_http_groonga_handler(ngx_http_request_t * r)1041 ngx_http_groonga_handler(ngx_http_request_t *r)
1042 {
1043 ngx_int_t rc;
1044
1045 switch (r->method) {
1046 case NGX_HTTP_GET:
1047 case NGX_HTTP_HEAD:
1048 rc = ngx_http_groonga_handler_get(r);
1049 break;
1050 case NGX_HTTP_POST:
1051 rc = ngx_http_read_client_request_body(r, ngx_http_groonga_handler_post);
1052 if (rc < NGX_HTTP_SPECIAL_RESPONSE) {
1053 rc = NGX_DONE;
1054 }
1055 break;
1056 default:
1057 rc = NGX_HTTP_NOT_ALLOWED;
1058 break;
1059 }
1060
1061 return rc;
1062 }
1063
1064 static char *
ngx_http_groonga_conf_set_groonga_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1065 ngx_http_groonga_conf_set_groonga_slot(ngx_conf_t *cf, ngx_command_t *cmd,
1066 void *conf)
1067 {
1068 char *status;
1069 ngx_http_core_loc_conf_t *location_conf;
1070 ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1071
1072 status = ngx_conf_set_flag_slot(cf, cmd, conf);
1073 if (status != NGX_CONF_OK) {
1074 return status;
1075 }
1076
1077 location_conf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
1078 if (groonga_location_conf->enabled) {
1079 location_conf->handler = ngx_http_groonga_handler;
1080 groonga_location_conf->name =
1081 ngx_str_null_terminate(cf->pool, &(location_conf->name));
1082 groonga_location_conf->config_file =
1083 ngx_str_null_terminate(cf->pool, &(cf->conf_file->file.name));
1084 groonga_location_conf->config_line = cf->conf_file->line;
1085 } else {
1086 location_conf->handler = NULL;
1087 }
1088
1089 return NGX_CONF_OK;
1090 }
1091
1092 static char *
ngx_http_groonga_conf_set_log_path_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1093 ngx_http_groonga_conf_set_log_path_slot(ngx_conf_t *cf, ngx_command_t *cmd,
1094 void *conf)
1095 {
1096 char *status;
1097 ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1098
1099 status = ngx_conf_set_str_slot(cf, cmd, conf);
1100 if (status != NGX_CONF_OK) {
1101 return status;
1102 }
1103
1104 if (!groonga_location_conf->log_path.data) {
1105 return NGX_CONF_OK;
1106 }
1107
1108 if (!ngx_str_is_custom_path(&(groonga_location_conf->log_path))) {
1109 return NGX_CONF_OK;
1110 }
1111
1112 groonga_location_conf->log_file =
1113 ngx_conf_open_file(cf->cycle, &(groonga_location_conf->log_path));
1114 if (!groonga_location_conf->log_file) {
1115 ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1116 "http_groonga: failed to open groonga log file: <%V>",
1117 &(groonga_location_conf->log_path));
1118 return NGX_CONF_ERROR;
1119 }
1120
1121 return NGX_CONF_OK;
1122 }
1123
1124 static char *
ngx_http_groonga_conf_set_log_level_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1125 ngx_http_groonga_conf_set_log_level_slot(ngx_conf_t *cf, ngx_command_t *cmd,
1126 void *conf)
1127 {
1128 char *status = NGX_CONF_OK;
1129 ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1130 char *value;
1131
1132 value = ngx_str_null_terminate(cf->cycle->pool,
1133 ((ngx_str_t *)cf->args->elts) + 1);
1134 if (!grn_log_level_parse(value, &(groonga_location_conf->log_level))) {
1135 status = "must be one of 'none', 'emergency', 'alert', "
1136 "'critical', 'error', 'warning', 'notice', 'info', 'debug' and 'dump'";
1137 }
1138 ngx_pfree(cf->cycle->pool, value);
1139
1140 return status;
1141 }
1142
1143 static char *
ngx_http_groonga_conf_set_query_log_path_slot(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1144 ngx_http_groonga_conf_set_query_log_path_slot(ngx_conf_t *cf,
1145 ngx_command_t *cmd,
1146 void *conf)
1147 {
1148 char *status;
1149 ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
1150
1151 status = ngx_conf_set_str_slot(cf, cmd, conf);
1152 if (status != NGX_CONF_OK) {
1153 return status;
1154 }
1155
1156 if (!groonga_location_conf->query_log_path.data) {
1157 return NGX_CONF_OK;
1158 }
1159
1160 if (!ngx_str_is_custom_path(&(groonga_location_conf->query_log_path))) {
1161 return NGX_CONF_OK;
1162 }
1163
1164 groonga_location_conf->query_log_file =
1165 ngx_conf_open_file(cf->cycle, &(groonga_location_conf->query_log_path));
1166 if (!groonga_location_conf->query_log_file) {
1167 ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1168 "http_groonga: failed to open Groonga query log file: <%V>",
1169 &(groonga_location_conf->query_log_path));
1170 return NGX_CONF_ERROR;
1171 }
1172
1173 return NGX_CONF_OK;
1174 }
1175
1176 static void *
ngx_http_groonga_create_loc_conf(ngx_conf_t * cf)1177 ngx_http_groonga_create_loc_conf(ngx_conf_t *cf)
1178 {
1179 ngx_http_groonga_loc_conf_t *conf;
1180 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_groonga_loc_conf_t));
1181 if (conf == NULL) {
1182 return NGX_CONF_ERROR;
1183 }
1184
1185 conf->enabled = NGX_CONF_UNSET;
1186 conf->database_path.data = NULL;
1187 conf->database_path.len = 0;
1188 conf->database_path_cstr = NULL;
1189 conf->database_auto_create = NGX_CONF_UNSET;
1190 conf->base_path.data = NULL;
1191 conf->base_path.len = 0;
1192 conf->log_path.data = NULL;
1193 conf->log_path.len = 0;
1194 conf->log_file = NULL;
1195 conf->log_level = GRN_LOG_DEFAULT_LEVEL;
1196 conf->query_log_path.data = NULL;
1197 conf->query_log_path.len = 0;
1198 conf->query_log_file = NULL;
1199 conf->cache_limit = NGX_CONF_UNSET_SIZE;
1200 conf->config_file = NULL;
1201 conf->config_line = 0;
1202 conf->cache = NULL;
1203 conf->cache_base_path.data = NULL;
1204 conf->cache_base_path.len = 0;
1205
1206 return conf;
1207 }
1208
1209 static char *
ngx_http_groonga_merge_loc_conf(ngx_conf_t * cf,void * parent,void * child)1210 ngx_http_groonga_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1211 {
1212 ngx_http_groonga_loc_conf_t *prev = parent;
1213 ngx_http_groonga_loc_conf_t *conf = child;
1214 ngx_flag_t enabled = 0;
1215
1216 if (conf->enabled != NGX_CONF_UNSET) {
1217 enabled = conf->enabled;
1218 }
1219
1220 ngx_conf_merge_str_value(conf->database_path, prev->database_path, NULL);
1221 ngx_conf_merge_value(conf->database_auto_create,
1222 prev->database_auto_create,
1223 GRN_TRUE);
1224 ngx_conf_merge_size_value(conf->cache_limit, prev->cache_limit,
1225 GRN_CACHE_DEFAULT_MAX_N_ENTRIES);
1226
1227 #ifdef NGX_HTTP_GROONGA_LOG_PATH
1228 ngx_conf_merge_str_value(conf->log_path, prev->log_path,
1229 NGX_HTTP_GROONGA_LOG_PATH);
1230 if (!conf->log_file &&
1231 ngx_str_is_custom_path(&(conf->log_path)) &&
1232 enabled) {
1233 conf->log_file = ngx_conf_open_file(cf->cycle, &(conf->log_path));
1234 if (!conf->log_file) {
1235 ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1236 "http_groonga: "
1237 "failed to open the default Groonga log file: <%V>",
1238 &(conf->log_path));
1239 return NGX_CONF_ERROR;
1240 }
1241 }
1242 #endif
1243
1244 ngx_conf_merge_str_value(conf->query_log_path, prev->query_log_path,
1245 NGX_HTTP_GROONGA_QUERY_LOG_PATH);
1246 if (!conf->query_log_file &&
1247 ngx_str_is_custom_path(&(conf->query_log_path)) &&
1248 enabled) {
1249 conf->query_log_file = ngx_conf_open_file(cf->cycle,
1250 &(conf->query_log_path));
1251 if (!conf->query_log_file) {
1252 ngx_log_error(NGX_LOG_ERR, cf->cycle->log, 0,
1253 "http_groonga: "
1254 "failed to open the default Groonga query log file: <%V>",
1255 &(conf->query_log_path));
1256 return NGX_CONF_ERROR;
1257 }
1258 }
1259
1260 ngx_conf_merge_str_value(conf->cache_base_path,
1261 prev->cache_base_path,
1262 NULL);
1263
1264 return NGX_CONF_OK;
1265 }
1266
1267 static void
ngx_http_groonga_each_loc_conf_in_tree(ngx_http_location_tree_node_t * node,ngx_http_groonga_loc_conf_callback_pt callback,void * user_data)1268 ngx_http_groonga_each_loc_conf_in_tree(ngx_http_location_tree_node_t *node,
1269 ngx_http_groonga_loc_conf_callback_pt callback,
1270 void *user_data)
1271 {
1272 if (!node) {
1273 return;
1274 }
1275
1276 if (node->exact && node->exact->handler == ngx_http_groonga_handler) {
1277 callback(node->exact->loc_conf[ngx_http_groonga_module.ctx_index],
1278 user_data);
1279 }
1280
1281 if (node->inclusive && node->inclusive->handler == ngx_http_groonga_handler) {
1282 callback(node->inclusive->loc_conf[ngx_http_groonga_module.ctx_index],
1283 user_data);
1284 }
1285
1286 ngx_http_groonga_each_loc_conf_in_tree(node->left, callback, user_data);
1287 ngx_http_groonga_each_loc_conf_in_tree(node->right, callback, user_data);
1288 ngx_http_groonga_each_loc_conf_in_tree(node->tree, callback, user_data);
1289 }
1290
1291 static void
ngx_http_groonga_each_loc_conf(ngx_http_conf_ctx_t * http_conf,ngx_http_groonga_loc_conf_callback_pt callback,void * user_data)1292 ngx_http_groonga_each_loc_conf(ngx_http_conf_ctx_t *http_conf,
1293 ngx_http_groonga_loc_conf_callback_pt callback,
1294 void *user_data)
1295 {
1296 ngx_http_core_main_conf_t *main_conf;
1297 ngx_http_core_srv_conf_t **server_confs;
1298 ngx_uint_t i;
1299
1300 if (!http_conf) {
1301 return;
1302 }
1303
1304 main_conf = http_conf->main_conf[ngx_http_core_module.ctx_index];
1305 server_confs = main_conf->servers.elts;
1306 for (i = 0; i < main_conf->servers.nelts; i++) {
1307 ngx_http_core_srv_conf_t *server_conf;
1308 ngx_http_core_loc_conf_t *location_conf;
1309
1310 server_conf = server_confs[i];
1311 location_conf = server_conf->ctx->loc_conf[ngx_http_core_module.ctx_index];
1312 ngx_http_groonga_each_loc_conf_in_tree(location_conf->static_locations,
1313 callback,
1314 user_data);
1315
1316 #if NGX_PCRE
1317 if (location_conf->regex_locations) {
1318 ngx_uint_t j;
1319 for (j = 0; location_conf->regex_locations[j]; j++) {
1320 ngx_http_core_loc_conf_t *regex_location_conf;
1321
1322 regex_location_conf = location_conf->regex_locations[j];
1323 if (regex_location_conf->handler == ngx_http_groonga_handler) {
1324 callback(regex_location_conf->loc_conf[ngx_http_groonga_module.ctx_index],
1325 user_data);
1326 }
1327 }
1328 }
1329 #endif
1330 }
1331 }
1332
1333 static void
ngx_http_groonga_set_logger_callback(ngx_http_groonga_loc_conf_t * location_conf,void * user_data)1334 ngx_http_groonga_set_logger_callback(ngx_http_groonga_loc_conf_t *location_conf,
1335 void *user_data)
1336 {
1337 ngx_http_groonga_database_callback_data_t *data = user_data;
1338
1339 data->rc = ngx_http_groonga_context_init_logger(location_conf,
1340 data->pool,
1341 data->log);
1342 if (data->rc != NGX_OK) {
1343 return;
1344 }
1345 data->rc = ngx_http_groonga_context_init_query_logger(location_conf,
1346 data->pool,
1347 data->log);
1348 if (data->rc != NGX_OK) {
1349 return;
1350 }
1351 }
1352
1353 static ngx_int_t
ngx_http_groonga_mkdir_p(ngx_log_t * log,const char * dir_name)1354 ngx_http_groonga_mkdir_p(ngx_log_t *log, const char *dir_name)
1355 {
1356 char sub_path[PATH_MAX];
1357 size_t i, dir_name_length;
1358
1359 dir_name_length = strlen(dir_name);
1360 sub_path[0] = dir_name[0];
1361 for (i = 1; i < dir_name_length + 1; i++) {
1362 if (dir_name[i] == '/' || dir_name[i] == '\0') {
1363 struct stat stat_buffer;
1364 sub_path[i] = '\0';
1365 if (stat(sub_path, &stat_buffer) == -1) {
1366 if (ngx_create_dir(sub_path, 0700) == -1) {
1367 ngx_log_error(NGX_LOG_EMERG, log, 0,
1368 "failed to create directory: %s (%s): %s",
1369 sub_path, dir_name,
1370 strerror(errno));
1371 return NGX_ERROR;
1372 }
1373 }
1374 }
1375 sub_path[i] = dir_name[i];
1376 }
1377
1378 return NGX_OK;
1379 }
1380
1381 static void
ngx_http_groonga_create_database(ngx_http_groonga_loc_conf_t * location_conf,ngx_http_groonga_database_callback_data_t * data)1382 ngx_http_groonga_create_database(ngx_http_groonga_loc_conf_t *location_conf,
1383 ngx_http_groonga_database_callback_data_t *data)
1384 {
1385 const char *database_base_name;
1386
1387 database_base_name = strrchr(location_conf->database_path_cstr, '/');
1388 if (database_base_name) {
1389 char database_dir[PATH_MAX];
1390 database_dir[0] = '\0';
1391 strncat(database_dir,
1392 location_conf->database_path_cstr,
1393 database_base_name - location_conf->database_path_cstr);
1394 data->rc = ngx_http_groonga_mkdir_p(data->log, database_dir);
1395 if (data->rc != NGX_OK) {
1396 return;
1397 }
1398 }
1399
1400 location_conf->database =
1401 grn_db_create(context, location_conf->database_path_cstr, NULL);
1402 if (context->rc == GRN_SUCCESS) {
1403 return;
1404 }
1405
1406 ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1407 "failed to create Groonga database: %s",
1408 context->errbuf);
1409 data->rc = NGX_ERROR;
1410 }
1411
1412 static void
ngx_http_groonga_open_database_callback(ngx_http_groonga_loc_conf_t * location_conf,void * user_data)1413 ngx_http_groonga_open_database_callback(ngx_http_groonga_loc_conf_t *location_conf,
1414 void *user_data)
1415 {
1416 ngx_http_groonga_database_callback_data_t *data = user_data;
1417
1418 data->rc = ngx_http_groonga_context_init_logger(location_conf,
1419 data->pool,
1420 data->log);
1421 if (data->rc != NGX_OK) {
1422 return;
1423 }
1424 data->rc = ngx_http_groonga_context_init_query_logger(location_conf,
1425 data->pool,
1426 data->log);
1427 if (data->rc != NGX_OK) {
1428 return;
1429 }
1430
1431 if (!location_conf->database_path.data) {
1432 ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1433 "%s: \"groonga_database\" must be specified in block at %s:%d",
1434 location_conf->name,
1435 location_conf->config_file,
1436 location_conf->config_line);
1437 data->rc = NGX_ERROR;
1438 return;
1439 }
1440
1441 if (!location_conf->database_path_cstr) {
1442 location_conf->database_path_cstr =
1443 ngx_str_null_terminate(data->pool, &(location_conf->database_path));
1444 }
1445
1446 location_conf->database =
1447 grn_db_open(context, location_conf->database_path_cstr);
1448 if (context->rc != GRN_SUCCESS) {
1449 if (location_conf->database_auto_create) {
1450 ngx_http_groonga_create_database(location_conf, data);
1451 } else {
1452 ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1453 "failed to open Groonga database: %s",
1454 context->errbuf);
1455 data->rc = NGX_ERROR;
1456 }
1457 if (data->rc != NGX_OK) {
1458 return;
1459 }
1460 }
1461
1462 if (location_conf->cache_base_path.data &&
1463 ngx_str_is_custom_path(&(location_conf->cache_base_path))) {
1464 char cache_base_path[PATH_MAX];
1465 grn_memcpy(cache_base_path,
1466 location_conf->cache_base_path.data,
1467 location_conf->cache_base_path.len);
1468 cache_base_path[location_conf->cache_base_path.len] = '\0';
1469 location_conf->cache = grn_persistent_cache_open(context, cache_base_path);
1470 } else {
1471 location_conf->cache = grn_cache_open(context);
1472 }
1473 if (!location_conf->cache) {
1474 ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1475 "failed to open Groonga cache: %s",
1476 context->errbuf);
1477 data->rc = NGX_ERROR;
1478 return;
1479 }
1480
1481 if (location_conf->cache_limit != NGX_CONF_UNSET_SIZE) {
1482 grn_cache_set_max_n_entries(context,
1483 location_conf->cache,
1484 location_conf->cache_limit);
1485 }
1486 }
1487
1488 static void
ngx_http_groonga_close_database_callback(ngx_http_groonga_loc_conf_t * location_conf,void * user_data)1489 ngx_http_groonga_close_database_callback(ngx_http_groonga_loc_conf_t *location_conf,
1490 void *user_data)
1491 {
1492 ngx_http_groonga_database_callback_data_t *data = user_data;
1493
1494 ngx_http_groonga_context_init_logger(location_conf,
1495 data->pool,
1496 data->log);
1497 ngx_http_groonga_context_init_query_logger(location_conf,
1498 data->pool,
1499 data->log);
1500 grn_cache_current_set(context, location_conf->cache);
1501
1502 grn_obj_close(context, location_conf->database);
1503 ngx_http_groonga_context_log_error(data->log);
1504
1505 grn_cache_current_set(context, NULL);
1506 grn_cache_close(context, location_conf->cache);
1507 }
1508
1509 static ngx_int_t
ngx_http_groonga_init_process(ngx_cycle_t * cycle)1510 ngx_http_groonga_init_process(ngx_cycle_t *cycle)
1511 {
1512 grn_rc rc;
1513 ngx_http_conf_ctx_t *http_conf;
1514 ngx_http_groonga_database_callback_data_t data;
1515
1516 grn_thread_set_get_limit_func(ngx_http_groonga_get_thread_limit, NULL);
1517
1518 #ifdef NGX_HTTP_GROONGA_LOG_PATH
1519 grn_default_logger_set_path(NGX_HTTP_GROONGA_LOG_PATH);
1520 #endif
1521
1522 http_conf =
1523 (ngx_http_conf_ctx_t *)ngx_get_conf(cycle->conf_ctx, ngx_http_module);
1524
1525 data.log = cycle->log;
1526 data.pool = cycle->pool;
1527 data.rc = NGX_OK;
1528 ngx_http_groonga_each_loc_conf(http_conf,
1529 ngx_http_groonga_set_logger_callback,
1530 &data);
1531
1532 if (data.rc != NGX_OK) {
1533 return data.rc;
1534 }
1535
1536 rc = grn_init();
1537 if (rc != GRN_SUCCESS) {
1538 return NGX_ERROR;
1539 }
1540
1541 grn_set_segv_handler();
1542
1543 rc = grn_ctx_init(context, GRN_NO_FLAGS);
1544 if (rc != GRN_SUCCESS) {
1545 return NGX_ERROR;
1546 }
1547
1548 ngx_http_groonga_each_loc_conf(http_conf,
1549 ngx_http_groonga_open_database_callback,
1550 &data);
1551
1552 return data.rc;
1553 }
1554
1555 static void
ngx_http_groonga_exit_process(ngx_cycle_t * cycle)1556 ngx_http_groonga_exit_process(ngx_cycle_t *cycle)
1557 {
1558 ngx_http_conf_ctx_t *http_conf;
1559 ngx_http_groonga_database_callback_data_t data;
1560
1561 http_conf =
1562 (ngx_http_conf_ctx_t *)ngx_get_conf(cycle->conf_ctx, ngx_http_module);
1563 data.log = cycle->log;
1564 data.pool = cycle->pool;
1565 ngx_http_groonga_each_loc_conf(http_conf,
1566 ngx_http_groonga_close_database_callback,
1567 &data);
1568
1569 grn_ctx_fin(context);
1570
1571 grn_fin();
1572
1573 return;
1574 }
1575
1576 /* entry point */
1577 static ngx_command_t ngx_http_groonga_commands[] = {
1578 { ngx_string("groonga"),
1579 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1580 ngx_http_groonga_conf_set_groonga_slot,
1581 NGX_HTTP_LOC_CONF_OFFSET,
1582 offsetof(ngx_http_groonga_loc_conf_t, enabled),
1583 NULL },
1584
1585 { ngx_string("groonga_database"),
1586 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1587 ngx_conf_set_str_slot,
1588 NGX_HTTP_LOC_CONF_OFFSET,
1589 offsetof(ngx_http_groonga_loc_conf_t, database_path),
1590 NULL },
1591
1592 { ngx_string("groonga_database_auto_create"),
1593 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1594 ngx_conf_set_flag_slot,
1595 NGX_HTTP_LOC_CONF_OFFSET,
1596 offsetof(ngx_http_groonga_loc_conf_t, database_auto_create),
1597 NULL },
1598
1599 { ngx_string("groonga_base_path"),
1600 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1601 ngx_conf_set_str_slot,
1602 NGX_HTTP_LOC_CONF_OFFSET,
1603 offsetof(ngx_http_groonga_loc_conf_t, base_path),
1604 NULL },
1605
1606 { ngx_string("groonga_log_path"),
1607 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1608 ngx_http_groonga_conf_set_log_path_slot,
1609 NGX_HTTP_LOC_CONF_OFFSET,
1610 offsetof(ngx_http_groonga_loc_conf_t, log_path),
1611 NULL },
1612
1613 { ngx_string("groonga_log_level"),
1614 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1615 ngx_http_groonga_conf_set_log_level_slot,
1616 NGX_HTTP_LOC_CONF_OFFSET,
1617 0,
1618 NULL },
1619
1620 { ngx_string("groonga_query_log_path"),
1621 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1622 ngx_http_groonga_conf_set_query_log_path_slot,
1623 NGX_HTTP_LOC_CONF_OFFSET,
1624 offsetof(ngx_http_groonga_loc_conf_t, query_log_path),
1625 NULL },
1626
1627 { ngx_string("groonga_cache_limit"),
1628 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1629 ngx_conf_set_size_slot,
1630 NGX_HTTP_LOC_CONF_OFFSET,
1631 offsetof(ngx_http_groonga_loc_conf_t, cache_limit),
1632 NULL },
1633
1634 { ngx_string("groonga_default_request_timeout"),
1635 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1636 ngx_conf_set_msec_slot,
1637 NGX_HTTP_LOC_CONF_OFFSET,
1638 offsetof(ngx_http_groonga_loc_conf_t, default_request_timeout_msec),
1639 NULL },
1640
1641 { ngx_string("groonga_cache_base_path"),
1642 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
1643 ngx_conf_set_str_slot,
1644 NGX_HTTP_LOC_CONF_OFFSET,
1645 offsetof(ngx_http_groonga_loc_conf_t, cache_base_path),
1646 NULL },
1647
1648 ngx_null_command
1649 };
1650
1651 static ngx_http_module_t ngx_http_groonga_module_ctx = {
1652 NULL, /* preconfiguration */
1653 NULL, /* postconfiguration */
1654
1655 NULL, /* create main configuration */
1656 NULL, /* init main configuration */
1657
1658 NULL, /* create server configuration */
1659 NULL, /* merge server configuration */
1660
1661 ngx_http_groonga_create_loc_conf, /* create location configuration */
1662 ngx_http_groonga_merge_loc_conf, /* merge location configuration */
1663 };
1664
1665 ngx_module_t ngx_http_groonga_module = {
1666 NGX_MODULE_V1,
1667 &ngx_http_groonga_module_ctx, /* module context */
1668 ngx_http_groonga_commands, /* module directives */
1669 NGX_HTTP_MODULE, /* module type */
1670 NULL, /* init master */
1671 NULL, /* init module */
1672 ngx_http_groonga_init_process, /* init process */
1673 NULL, /* init thread */
1674 NULL, /* exit thread */
1675 ngx_http_groonga_exit_process, /* exit process */
1676 NULL, /* exit master */
1677 NGX_MODULE_V1_PADDING
1678 };
1679