Groonga 3.0.9 Source Code Document
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ngx_http_groonga_module.c
Go to the documentation of this file.
1 /* -*- c-basic-offset: 2 -*- */
2 /*
3  Copyright(C) 2012-2013 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-1301 USA
17 */
18 
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 
23 #include <ngx_config.h>
24 #include <ngx_core.h>
25 #include <ngx_http.h>
26 
27 #include <groonga.h>
28 
29 #define GRN_NO_FLAGS 0
30 
31 typedef struct {
42  size_t cache_limit;
43  char *config_file;
45  char *name;
49 
50 typedef struct {
55 
56 typedef struct {
63 
64 typedef struct {
68 
69 typedef struct {
74 
76 
78 
79 static char *
80 ngx_str_null_terminate(ngx_pool_t *pool, const ngx_str_t *string)
81 {
82  char *null_terminated_c_string;
83 
84  null_terminated_c_string = ngx_pnalloc(pool, string->len + 1);
85  if (!null_terminated_c_string) {
86  return NULL;
87  }
88 
89  memcpy(null_terminated_c_string, string->data, string->len);
90  null_terminated_c_string[string->len] = '\0';
91 
92  return null_terminated_c_string;
93 }
94 
95 static grn_bool
96 ngx_str_equal_c_string(ngx_str_t *string, const char *c_string)
97 {
98  if (string->len != strlen(c_string)) {
99  return GRN_FALSE;
100  }
101 
102  return memcmp(c_string, string->data, string->len) == 0;
103 }
104 
105 static void
106 ngx_http_groonga_logger_log(grn_ctx *ctx, grn_log_level level,
107  const char *timestamp, const char *title,
108  const char *message, const char *location,
109  void *user_data)
110 {
111  ngx_http_groonga_logger_data_t *logger_data = user_data;
112  const char level_marks[] = " EACewnid-";
113  u_char buffer[NGX_MAX_ERROR_STR];
114  u_char *last;
115 
116  if (location && *location) {
117  last = ngx_slprintf(buffer, buffer + NGX_MAX_ERROR_STR,
118  "%s|%c|%s %s %s\n",
119  timestamp, *(level_marks + level), title, message,
120  location);
121  } else {
122  last = ngx_slprintf(buffer, buffer + NGX_MAX_ERROR_STR,
123  "%s|%c|%s %s\n",
124  timestamp, *(level_marks + level), title, message);
125  }
126  ngx_write_fd(logger_data->file->fd, buffer, last - buffer);
127 }
128 
129 static void
130 ngx_http_groonga_logger_reopen(grn_ctx *ctx, void *user_data)
131 {
132  GRN_LOG(ctx, GRN_LOG_NOTICE, "log will be closed.");
134  GRN_LOG(ctx, GRN_LOG_NOTICE, "log opened.");
135 }
136 
137 static void
138 ngx_http_groonga_logger_fin(grn_ctx *ctx, void *user_data)
139 {
140  ngx_http_groonga_logger_data_t *logger_data = user_data;
141 
142  ngx_pfree(logger_data->pool, logger_data);
143 }
144 
145 static grn_logger ngx_http_groonga_logger = {
148  NULL,
149  ngx_http_groonga_logger_log,
150  ngx_http_groonga_logger_reopen,
151  ngx_http_groonga_logger_fin
152 };
153 
154 static ngx_int_t
155 ngx_http_groonga_context_init_logger(grn_ctx *context,
156  ngx_http_groonga_loc_conf_t *location_conf,
157  ngx_pool_t *pool,
158  ngx_log_t *log)
159 {
160  ngx_http_groonga_logger_data_t *logger_data;
161 
162  if (!location_conf->log_file) {
163  return NGX_OK;
164  }
165 
166  logger_data = ngx_pcalloc(pool, sizeof(ngx_http_groonga_logger_data_t));
167  if (!logger_data) {
168  ngx_log_error(NGX_LOG_ERR, log, 0,
169  "http_groonga: failed to allocate memory for logger");
170  return NGX_ERROR;
171  }
172 
173  logger_data->pool = pool;
174  logger_data->file = location_conf->log_file;
175  ngx_http_groonga_logger.max_level = location_conf->log_level;
176  ngx_http_groonga_logger.user_data = logger_data;
177  grn_logger_set(context, &ngx_http_groonga_logger);
178 
179  return NGX_OK;
180 }
181 
182 static void
183 ngx_http_groonga_query_logger_log(grn_ctx *ctx, unsigned int flag,
184  const char *timestamp, const char *info,
185  const char *message, void *user_data)
186 {
187  ngx_http_groonga_query_logger_data_t *data = user_data;
188  u_char buffer[NGX_MAX_ERROR_STR];
189  u_char *last;
190 
191  last = ngx_slprintf(buffer, buffer + NGX_MAX_ERROR_STR,
192  "%s|%s%s\n",
193  timestamp, info, message);
194  ngx_write_fd(data->file->fd, buffer, last - buffer);
195 }
196 
197 static void
198 ngx_http_groonga_query_logger_reopen(grn_ctx *ctx, void *user_data)
199 {
200  ngx_http_groonga_query_logger_data_t *data = user_data;
201 
203  "query log will be closed: <%.*s>",
204  (int)(data->path->len), data->path->data);
207  "query log is opened: <%.*s>",
208  (int)(data->path->len), data->path->data);
209 }
210 
211 static void
212 ngx_http_groonga_query_logger_fin(grn_ctx *ctx, void *user_data)
213 {
214  ngx_http_groonga_query_logger_data_t *data = user_data;
215 
216  ngx_pfree(data->pool, data);
217 }
218 
219 static grn_query_logger ngx_http_groonga_query_logger = {
221  NULL,
222  ngx_http_groonga_query_logger_log,
223  ngx_http_groonga_query_logger_reopen,
224  ngx_http_groonga_query_logger_fin
225 };
226 
227 static ngx_int_t
228 ngx_http_groonga_context_init_query_logger(grn_ctx *context,
229  ngx_http_groonga_loc_conf_t *location_conf,
230  ngx_pool_t *pool,
231  ngx_log_t *log)
232 {
233  ngx_http_groonga_query_logger_data_t *query_logger_data;
234 
235  if (!location_conf->query_log_file) {
236  return NGX_OK;
237  }
238 
239  query_logger_data = ngx_pcalloc(pool,
241  if (!query_logger_data) {
242  ngx_log_error(NGX_LOG_ERR, log, 0,
243  "http_groonga: failed to allocate memory for query logger");
244  return NGX_ERROR;
245  }
246 
247  query_logger_data->pool = pool;
248  query_logger_data->file = location_conf->query_log_file;
249  query_logger_data->path = &(location_conf->query_log_path);
250  ngx_http_groonga_query_logger.user_data = query_logger_data;
251  grn_query_logger_set(context, &ngx_http_groonga_query_logger);
252 
253  return NGX_OK;
254 }
255 
256 static ngx_int_t
257 ngx_http_groonga_context_init(grn_ctx *context,
258  ngx_http_groonga_loc_conf_t *location_conf,
259  ngx_pool_t *pool,
260  ngx_log_t *log)
261 {
262  ngx_int_t status;
263 
264  grn_ctx_init(context, GRN_NO_FLAGS);
265 
266  status = ngx_http_groonga_context_init_logger(context,
267  location_conf,
268  pool,
269  log);
270  if (status == NGX_ERROR) {
271  grn_ctx_fin(context);
272  return status;
273  }
274 
275  status = ngx_http_groonga_context_init_query_logger(context,
276  location_conf,
277  pool,
278  log);
279  if (status == NGX_ERROR) {
280  grn_ctx_fin(context);
281  return status;
282  }
283 
284  if (location_conf->cache) {
285  grn_cache_current_set(context, location_conf->cache);
286  }
287 
288  return status;
289 }
290 
291 static void
292 ngx_http_groonga_context_log_error(ngx_log_t *log, grn_ctx *context)
293 {
294  if (context->rc == GRN_SUCCESS) {
295  return;
296  }
297 
298  ngx_log_error(NGX_LOG_ERR, log, 0, "%s", context->errbuf);
299 }
300 
301 static ngx_int_t
302 ngx_http_groonga_context_check_error(ngx_log_t *log, grn_ctx *context)
303 {
304  if (context->rc == GRN_SUCCESS) {
305  return NGX_OK;
306  } else {
307  ngx_http_groonga_context_log_error(log, context);
308  return NGX_HTTP_BAD_REQUEST;
309  }
310 }
311 
312 static ngx_buf_t *
313 ngx_http_groonga_grn_obj_to_ngx_buf(ngx_pool_t *pool, grn_obj *object)
314 {
315  ngx_buf_t *buffer;
316  buffer = ngx_pcalloc(pool, sizeof(ngx_buf_t));
317  if (buffer == NULL) {
318  return NULL;
319  }
320 
321  /* adjust the pointers of the buffer */
322  buffer->pos = (u_char *)GRN_TEXT_VALUE(object);
323  buffer->last = (u_char *)GRN_TEXT_VALUE(object) + GRN_TEXT_LEN(object);
324  buffer->memory = 1; /* this buffer is in memory */
325 
326  return buffer;
327 }
328 
329 static void
330 ngx_http_groonga_handler_cleanup(void *user_data)
331 {
332  ngx_http_groonga_handler_data_t *data = user_data;
333  grn_ctx *context;
334 
335  if (!data->initialized) {
336  return;
337  }
338 
339  context = &(data->context);
340  GRN_OBJ_FIN(context, &(data->head));
341  GRN_OBJ_FIN(context, &(data->body));
342  GRN_OBJ_FIN(context, &(data->foot));
343  grn_logger_set(context, NULL);
344  grn_query_logger_set(context, NULL);
345  grn_ctx_fin(context);
346 }
347 
348 static void
349 ngx_http_groonga_context_receive_handler(grn_ctx *context,
350  int flags,
351  void *callback_data)
352 {
353  ngx_http_groonga_handler_data_t *data = callback_data;
354  char *result = NULL;
355  unsigned int result_size = 0;
356  int recv_flags;
357 
358  if (!(flags & GRN_CTX_TAIL)) {
359  return;
360  }
361 
362  grn_ctx_recv(context, &result, &result_size, &recv_flags);
363 
364  if (recv_flags == GRN_CTX_QUIT) {
365  ngx_int_t ngx_rc;
367 
369  ngx_pid = getpid();
370  } else {
371  ngx_pid = getppid();
372  }
373 
375  "stop",
376  ngx_pid);
377  if (ngx_rc == NGX_OK) {
378  context->stat &= ~GRN_CTX_QUIT;
379  grn_ctx_recv(context, &result, &result_size, &recv_flags);
380  context->stat |= GRN_CTX_QUIT;
381  } else {
382  context->rc = GRN_OPERATION_NOT_PERMITTED;
383  GRN_TEXT_PUTS(context, &(data->body), "false");
384  context->stat &= ~GRN_CTX_QUIT;
385  }
386  }
387 
388  if (result_size > 0 ||
389  GRN_TEXT_LEN(&(data->body)) > 0 ||
390  context->rc != GRN_SUCCESS) {
391  if (result_size > 0) {
392  GRN_TEXT_PUT(context, &(data->body), result, result_size);
393  }
394 
395  grn_output_envelope(context,
396  context->rc,
397  &(data->head),
398  &(data->body),
399  &(data->foot),
400  NULL,
401  0);
402  }
403 }
404 
405 static ngx_int_t
406 ngx_http_groonga_extract_command_path(ngx_http_request_t *r,
407  ngx_str_t *command_path)
408 {
409  size_t base_path_length;
410 
411  ngx_http_core_loc_conf_t *http_location_conf;
412  ngx_http_groonga_loc_conf_t *location_conf;
413 
414  http_location_conf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
415  location_conf = ngx_http_get_module_loc_conf(r, ngx_http_groonga_module);
416 
417  command_path->data = r->unparsed_uri.data;
418  command_path->len = r->unparsed_uri.len;
419  base_path_length = http_location_conf->name.len;
420  if (location_conf->base_path.len > 0) {
421  if (command_path->len < location_conf->base_path.len) {
423  "requested URI is shorter than groonga_base_path: "
424  "URI: <%V>, groonga_base_path: <%V>",
425  &(r->unparsed_uri), &(location_conf->base_path));
426  } else if (strncmp((const char *)command_path->data,
427  (const char *)(location_conf->base_path.data),
428  location_conf->base_path.len) < 0) {
430  "groonga_base_path doesn't match requested URI: "
431  "URI: <%V>, groonga_base_path: <%V>",
432  &(r->unparsed_uri), &(location_conf->base_path));
433  } else {
434  base_path_length = location_conf->base_path.len;
435  }
436  }
437  command_path->data += base_path_length;
438  command_path->len -= base_path_length;
439  if (command_path->len > 0 && command_path->data[0] == '/') {
440  command_path->data += 1;
441  command_path->len -= 1;
442  }
443  if (command_path->len == 0) {
444  return NGX_HTTP_BAD_REQUEST;
445  }
446 
447  return NGX_OK;
448 }
449 
450 static void
451 ngx_http_groonga_handler_set_content_type(ngx_http_request_t *r,
452  const char *content_type)
453 {
454  r->headers_out.content_type.len = strlen(content_type);
455  r->headers_out.content_type.data = (u_char *)content_type;
457 }
458 
459 static ngx_int_t
460 ngx_http_groonga_handler_create_data(ngx_http_request_t *r,
461  ngx_http_groonga_handler_data_t **data_return)
462 {
463  ngx_int_t rc;
464 
465  ngx_http_groonga_loc_conf_t *location_conf;
466 
467  ngx_http_cleanup_t *cleanup;
469 
470  grn_ctx *context;
471 
472  location_conf = ngx_http_get_module_loc_conf(r, ngx_http_groonga_module);
473 
475  cleanup->handler = ngx_http_groonga_handler_cleanup;
476  data = cleanup->data;
477  *data_return = data;
478 
479  context = &(data->context);
480  rc = ngx_http_groonga_context_init(context, location_conf,
481  r->pool, r->connection->log);
482  if (rc != NGX_OK) {
483  return rc;
484  }
485  data->initialized = GRN_TRUE;
486  GRN_TEXT_INIT(&(data->head), GRN_NO_FLAGS);
487  GRN_TEXT_INIT(&(data->body), GRN_NO_FLAGS);
488  GRN_TEXT_INIT(&(data->foot), GRN_NO_FLAGS);
489  grn_ctx_use(context, grn_ctx_db(&(location_conf->context)));
490  rc = ngx_http_groonga_context_check_error(r->connection->log, context);
491  if (rc != NGX_OK) {
492  return rc;
493  }
494 
495  grn_ctx_recv_handler_set(context,
496  ngx_http_groonga_context_receive_handler,
497  data);
498 
499  return NGX_OK;
500 }
501 
502 static ngx_int_t
503 ngx_http_groonga_handler_process_command_path(ngx_http_request_t *r,
504  ngx_str_t *command_path,
506 {
507  grn_ctx *context;
508  grn_obj uri;
509 
510  context = &(data->context);
511  GRN_TEXT_INIT(&uri, 0);
512  GRN_TEXT_PUTS(context, &uri, "/d/");
513  GRN_TEXT_PUT(context, &uri, command_path->data, command_path->len);
514  grn_ctx_send(context, GRN_TEXT_VALUE(&uri), GRN_TEXT_LEN(&uri),
515  GRN_NO_FLAGS);
516  ngx_http_groonga_context_log_error(r->connection->log, context);
517  GRN_OBJ_FIN(context, &uri);
518 
519  return NGX_OK;
520 }
521 
522 static ngx_int_t
523 ngx_http_groonga_handler_validate_post_command(ngx_http_request_t *r,
524  ngx_str_t *command_path,
526 {
527  grn_ctx *context;
528  ngx_str_t command;
529 
530  command.data = command_path->data;
531  if (r->args.len == 0) {
532  command.len = command_path->len;
533  } else {
534  command.len = command_path->len - r->args.len - strlen("?");
535  }
536  if (ngx_str_equal_c_string(&command, "load")) {
537  return NGX_OK;
538  }
539 
540  context = &(data->context);
541  ngx_http_groonga_handler_set_content_type(r, "text/plain");
542  GRN_TEXT_PUTS(context, &(data->body), "command for POST must be <load>: <");
543  GRN_TEXT_PUT(context, &(data->body), command.data, command.len);
544  GRN_TEXT_PUTS(context, &(data->body), ">");
545 
546  return NGX_HTTP_BAD_REQUEST;
547 }
548 
549 static ngx_int_t
550 ngx_http_groonga_send_lines(grn_ctx *context,
552  u_char *current,
553  u_char *last)
554 {
555  ngx_int_t rc;
556 
557  u_char *line_start;
558 
559  for (line_start = current; current < last; current++) {
560  if (*current != '\n') {
561  continue;
562  }
563 
564  grn_ctx_send(context, (const char *)line_start, current - line_start,
565  GRN_NO_FLAGS);
566  rc = ngx_http_groonga_context_check_error(r->connection->log, context);
567  if (rc != NGX_OK) {
568  return rc;
569  }
570  line_start = current + 1;
571  }
572  if (line_start < current) {
573  grn_ctx_send(context, (const char *)line_start, current - line_start,
574  GRN_NO_FLAGS);
575  rc = ngx_http_groonga_context_check_error(r->connection->log, context);
576  if (rc != NGX_OK) {
577  return rc;
578  }
579  }
580 
581  return NGX_OK;
582 }
583 
584 static ngx_int_t
585 ngx_http_groonga_join_request_body_chain(ngx_http_request_t *r,
586  ngx_chain_t *chain,
587  u_char **out_start,
588  u_char **out_end)
589 {
590  ngx_int_t rc;
591 
592  ngx_log_t *log = r->connection->log;
593 
594  ngx_chain_t *current;
595  u_char *out;
596  size_t out_size;
597 
598  u_char *out_cursor;
599  ngx_buf_t *buffer;
600  size_t buffer_size;
601 
602  out_size = 0;
603  for (current = chain; current; current = current->next) {
604  out_size += ngx_buf_size(current->buf);
605  }
606  out = ngx_palloc(r->pool, out_size);
607  if (!out) {
608  ngx_log_error(NGX_LOG_ERR, log, 0,
609  "http_groonga: failed to allocate memory for request body");
610  return NGX_ERROR;
611  }
612 
613  out_cursor = out;
614  for (current = chain; current; current = current->next) {
615  buffer = current->buf;
616  buffer_size = ngx_buf_size(current->buf);
617 
618  if (buffer->file) {
619  rc = ngx_read_file(buffer->file, out_cursor, buffer_size, 0);
620  if (rc < 0) {
621  ngx_log_error(NGX_LOG_ERR, log, 0,
622  "http_groonga: failed to read a request body stored in a file");
623  return rc;
624  }
625  } else {
626  ngx_memcpy(out_cursor, buffer->pos, buffer_size);
627  }
628  out_cursor += buffer_size;
629  }
630 
631  *out_start = out;
632  *out_end = out + out_size;
633 
634  return NGX_OK;
635 }
636 
637 static ngx_int_t
638 ngx_http_groonga_handler_process_body(ngx_http_request_t *r,
640 {
641  ngx_int_t rc;
642 
643  grn_ctx *context;
644 
645  ngx_buf_t *body;
646  u_char *body_data;
647  u_char *body_data_end;
648 
649  context = &(data->context);
650 
651  body = r->request_body->bufs->buf;
652  if (!body) {
653  ngx_http_groonga_handler_set_content_type(r, "text/plain");
654  GRN_TEXT_PUTS(context, &(data->body), "must send load data as body");
655  return NGX_HTTP_BAD_REQUEST;
656  }
657 
658  rc = ngx_http_groonga_join_request_body_chain(r,
659  r->request_body->bufs,
660  &body_data,
661  &body_data_end);
662  if (rc != NGX_OK) {
663  return rc;
664  }
665 
666  rc = ngx_http_groonga_send_lines(context, r, body_data, body_data_end);
667  ngx_pfree(r->pool, body_data);
668 
669  return rc;
670 }
671 
672 
673 static ngx_int_t
674 ngx_http_groonga_handler_process_load(ngx_http_request_t *r,
675  ngx_str_t *command_path,
677 {
678  ngx_int_t rc;
679 
680  rc = ngx_http_groonga_handler_validate_post_command(r, command_path, data);
681  if (rc != NGX_OK) {
682  return rc;
683  }
684 
685  rc = ngx_http_groonga_handler_process_command_path(r, command_path, data);
686  if (rc != NGX_OK) {
687  return rc;
688  }
689 
690  rc = ngx_http_groonga_handler_process_body(r, data);
691  if (rc != NGX_OK) {
692  return rc;
693  }
694 
695  return NGX_OK;
696 }
697 
698 static ngx_chain_t *
699 ngx_http_groonga_attach_chain(ngx_chain_t *chain, ngx_chain_t *new_chain)
700 {
701  ngx_chain_t *last_chain;
702 
703  if (new_chain->buf->last == new_chain->buf->pos) {
704  return chain;
705  }
706 
707  new_chain->buf->last_buf = 1;
708  new_chain->next = NULL;
709  if (!chain) {
710  return new_chain;
711  }
712 
713  chain->buf->last_buf = 0;
714  last_chain = chain;
715  while (last_chain->next) {
716  last_chain = last_chain->next;
717  }
718  last_chain->next = new_chain;
719  return chain;
720 }
721 
722 static ngx_int_t
723 ngx_http_groonga_handler_send_response(ngx_http_request_t *r,
725 {
726  ngx_int_t rc;
727  grn_ctx *context;
728  const char *content_type;
729  ngx_buf_t *head_buf, *body_buf, *foot_buf;
730  ngx_chain_t head_chain, body_chain, foot_chain;
731  ngx_chain_t *output_chain = NULL;
732 
733  context = &(data->context);
734 
735  /* set the 'Content-type' header */
736  if (r->headers_out.content_type.len == 0) {
737  content_type = grn_ctx_get_mime_type(context);
738  ngx_http_groonga_handler_set_content_type(r, content_type);
739  }
740 
741  /* allocate buffers for a response body */
742  head_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->head));
743  if (!head_buf) {
745  }
746 
747  body_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->body));
748  if (!body_buf) {
750  }
751 
752  foot_buf = ngx_http_groonga_grn_obj_to_ngx_buf(r->pool, &(data->foot));
753  if (!foot_buf) {
755  }
756 
757  /* attach buffers to the buffer chain */
758  head_chain.buf = head_buf;
759  output_chain = ngx_http_groonga_attach_chain(output_chain, &head_chain);
760  body_chain.buf = body_buf;
761  output_chain = ngx_http_groonga_attach_chain(output_chain, &body_chain);
762  foot_chain.buf = foot_buf;
763  output_chain = ngx_http_groonga_attach_chain(output_chain, &foot_chain);
764 
765  /* set the status line */
768  GRN_TEXT_LEN(&(data->body)) +
769  GRN_TEXT_LEN(&(data->foot));
770 
771  /* send the headers of your response */
772  rc = ngx_http_send_header(r);
773 
774  if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
775  return rc;
776  }
777 
778  /* send the buffer chain of your response */
779  rc = ngx_http_output_filter(r, output_chain);
780 
781  return rc;
782 }
783 
784 static ngx_int_t
785 ngx_http_groonga_handler_get(ngx_http_request_t *r)
786 {
787  ngx_int_t rc;
788  ngx_str_t command_path;
790 
791  rc = ngx_http_groonga_extract_command_path(r, &command_path);
792  if (rc != NGX_OK) {
793  return rc;
794  }
795 
796  rc = ngx_http_groonga_handler_create_data(r, &data);
797  if (rc != NGX_OK) {
798  return rc;
799  }
800 
801  rc = ngx_http_groonga_handler_process_command_path(r, &command_path, data);
802  if (rc != NGX_OK) {
803  return rc;
804  }
805 
806  /* discard request body, since we don't need it here */
808  if (rc != NGX_OK) {
809  return rc;
810  }
811 
812  rc = ngx_http_groonga_handler_send_response(r, data);
813 
814  return rc;
815 }
816 
817 static void
818 ngx_http_groonga_handler_post(ngx_http_request_t *r)
819 {
820  ngx_int_t rc;
821  ngx_str_t command_path;
823 
824  rc = ngx_http_groonga_extract_command_path(r, &command_path);
825  if (rc == NGX_OK) {
826  rc = ngx_http_groonga_handler_create_data(r, &data);
827  }
828  if (rc == NGX_OK) {
829  rc = ngx_http_groonga_handler_process_load(r, &command_path, data);
830  }
831 
832  ngx_http_groonga_handler_send_response(r, data);
834 }
835 
836 static ngx_int_t
837 ngx_http_groonga_handler(ngx_http_request_t *r)
838 {
839  ngx_int_t rc;
840 
841  switch (r->method) {
842  case NGX_HTTP_GET:
843  case NGX_HTTP_HEAD:
844  rc = ngx_http_groonga_handler_get(r);
845  break;
846  case NGX_HTTP_POST:
847  rc = ngx_http_read_client_request_body(r, ngx_http_groonga_handler_post);
848  if (rc < NGX_HTTP_SPECIAL_RESPONSE) {
849  rc = NGX_DONE;
850  }
851  break;
852  default:
854  break;
855  }
856 
857  return rc;
858 }
859 
860 static char *
861 ngx_http_groonga_conf_set_groonga_slot(ngx_conf_t *cf, ngx_command_t *cmd,
862  void *conf)
863 {
864  char *status;
865  ngx_http_core_loc_conf_t *location_conf;
866  ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
867 
868  status = ngx_conf_set_flag_slot(cf, cmd, conf);
869  if (status != NGX_CONF_OK) {
870  return status;
871  }
872 
874  if (groonga_location_conf->enabled) {
875  location_conf->handler = ngx_http_groonga_handler;
876  groonga_location_conf->name =
877  ngx_str_null_terminate(cf->pool, &(location_conf->name));
878  groonga_location_conf->config_file =
879  ngx_str_null_terminate(cf->pool, &(cf->conf_file->file.name));
880  groonga_location_conf->config_line = cf->conf_file->line;
881  } else {
882  location_conf->handler = NULL;
883  }
884 
885  return NGX_CONF_OK;
886 }
887 
888 static char *
889 ngx_http_groonga_conf_set_log_path_slot(ngx_conf_t *cf, ngx_command_t *cmd,
890  void *conf)
891 {
892  char *status;
893  ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
894 
895  status = ngx_conf_set_str_slot(cf, cmd, conf);
896  if (status != NGX_CONF_OK) {
897  return status;
898  }
899 
900  if (!groonga_location_conf->log_path.data) {
901  return NGX_CONF_OK;
902  }
903 
904  if (strncmp((const char *)(groonga_location_conf->log_path.data),
905  "off",
906  groonga_location_conf->log_path.len) == 0) {
907  return NGX_CONF_OK;
908  }
909 
910  groonga_location_conf->log_file =
911  ngx_conf_open_file(cf->cycle, &(groonga_location_conf->log_path));
912  if (!groonga_location_conf->log_file) {
914  "http_groonga: failed to open groonga log file: <%V>",
915  &(groonga_location_conf->log_path));
916  return NGX_CONF_ERROR;
917  }
918 
919  return NGX_CONF_OK;
920 }
921 
922 static char *
923 ngx_http_groonga_conf_set_log_level_slot(ngx_conf_t *cf, ngx_command_t *cmd,
924  void *conf)
925 {
926  char *status = NGX_CONF_OK;
927  ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
928  char *value;
929 
930  value = ngx_str_null_terminate(cf->cycle->pool,
931  ((ngx_str_t *)cf->args->elts) + 1);
932  if (strcasecmp(value, "none") == 0) {
933  groonga_location_conf->log_level = GRN_LOG_NONE;
934  } else if (strcasecmp(value, "emergency") == 0) {
935  groonga_location_conf->log_level = GRN_LOG_EMERG;
936  } else if (strcasecmp(value, "alert") == 0) {
937  groonga_location_conf->log_level = GRN_LOG_ALERT;
938  } else if (strcasecmp(value, "critical") == 0) {
939  groonga_location_conf->log_level = GRN_LOG_CRIT;
940  } else if (strcasecmp(value, "error") == 0) {
941  groonga_location_conf->log_level = GRN_LOG_ERROR;
942  } else if (strcasecmp(value, "warning") == 0) {
943  groonga_location_conf->log_level = GRN_LOG_WARNING;
944  } else if (strcasecmp(value, "notice") == 0) {
945  groonga_location_conf->log_level = GRN_LOG_NOTICE;
946  } else if (strcasecmp(value, "info") == 0) {
947  groonga_location_conf->log_level = GRN_LOG_INFO;
948  } else if (strcasecmp(value, "debug") == 0) {
949  groonga_location_conf->log_level = GRN_LOG_DEBUG;
950  } else if (strcasecmp(value, "dump") == 0) {
951  groonga_location_conf->log_level = GRN_LOG_DUMP;
952  } else {
953  status = "must be one of 'none', 'emergency', 'alert', "
954  "'ciritical', 'error', 'warning', 'notice', 'info', 'debug' and 'dump'";
955  }
956  ngx_pfree(cf->cycle->pool, value);
957 
958  return status;
959 }
960 
961 static char *
962 ngx_http_groonga_conf_set_query_log_path_slot(ngx_conf_t *cf,
963  ngx_command_t *cmd,
964  void *conf)
965 {
966  char *status;
967  ngx_http_groonga_loc_conf_t *groonga_location_conf = conf;
968 
969  status = ngx_conf_set_str_slot(cf, cmd, conf);
970  if (status != NGX_CONF_OK) {
971  return status;
972  }
973 
974  if (!groonga_location_conf->query_log_path.data) {
975  return NGX_CONF_OK;
976  }
977 
978  if (strncmp((const char *)(groonga_location_conf->query_log_path.data),
979  "off",
980  groonga_location_conf->query_log_path.len) == 0) {
981  return NGX_CONF_OK;
982  }
983 
984  groonga_location_conf->query_log_file =
985  ngx_conf_open_file(cf->cycle, &(groonga_location_conf->query_log_path));
986  if (!groonga_location_conf->query_log_file) {
988  "http_groonga: failed to open groonga query log file: <%V>",
989  &(groonga_location_conf->query_log_path));
990  return NGX_CONF_ERROR;
991  }
992 
993  return NGX_CONF_OK;
994 }
995 
996 static void *
997 ngx_http_groonga_create_loc_conf(ngx_conf_t *cf)
998 {
1000  conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_groonga_loc_conf_t));
1001  if (conf == NULL) {
1002  return NGX_CONF_ERROR;
1003  }
1004 
1005  conf->enabled = NGX_CONF_UNSET;
1006  conf->database_path.data = NULL;
1007  conf->database_path.len = 0;
1008  conf->database_path_cstr = NULL;
1010  conf->base_path.data = NULL;
1011  conf->base_path.len = 0;
1012  conf->log_path.data = NULL;
1013  conf->log_path.len = 0;
1014  conf->log_file = NULL;
1016  conf->query_log_path.data = NULL;
1017  conf->query_log_path.len = 0;
1018  conf->query_log_file = NULL;
1020  conf->config_file = NULL;
1021  conf->config_line = 0;
1022  conf->cache = NULL;
1023 
1024  return conf;
1025 }
1026 
1027 static char *
1028 ngx_http_groonga_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1029 {
1030  ngx_http_groonga_loc_conf_t *prev = parent;
1031  ngx_http_groonga_loc_conf_t *conf = child;
1032 
1035  prev->database_auto_create,
1036  GRN_TRUE);
1039 
1040 #ifdef NGX_HTTP_GROONGA_LOG_PATH
1041  {
1042  ngx_str_t default_log_path;
1043  default_log_path.data = (u_char *)NGX_HTTP_GROONGA_LOG_PATH;
1044  default_log_path.len = strlen(NGX_HTTP_GROONGA_LOG_PATH);
1045  conf->log_file = ngx_conf_open_file(cf->cycle, &default_log_path);
1046  if (!conf->log_file) {
1048  "http_groonga: "
1049  "failed to open the default groonga log file: <%V>",
1050  &default_log_path);
1051  return NGX_CONF_ERROR;
1052  }
1053  }
1054 #else
1055  conf->log_file = NULL;
1056 #endif
1057 
1059  NGX_HTTP_GROONGA_QUERY_LOG_PATH);
1060  if (!conf->query_log_file && conf->query_log_path.data && conf->enabled) {
1062  &(conf->query_log_path));
1063  if (!conf->query_log_file) {
1065  "http_groonga: "
1066  "failed to open the default groonga query log file: <%V>",
1067  &(conf->query_log_path));
1068  return NGX_CONF_ERROR;
1069  }
1070  }
1071 
1072  return NGX_CONF_OK;
1073 }
1074 
1075 static void
1076 ngx_http_groonga_each_loc_conf_in_tree(ngx_http_location_tree_node_t *node,
1078  void *user_data)
1079 {
1080  if (!node) {
1081  return;
1082  }
1083 
1084  if (node->exact && node->exact->handler == ngx_http_groonga_handler) {
1085  callback(node->exact->loc_conf[ngx_http_groonga_module.ctx_index],
1086  user_data);
1087  }
1088 
1089  if (node->inclusive && node->inclusive->handler == ngx_http_groonga_handler) {
1090  callback(node->inclusive->loc_conf[ngx_http_groonga_module.ctx_index],
1091  user_data);
1092  }
1093 
1094  ngx_http_groonga_each_loc_conf_in_tree(node->left, callback, user_data);
1095  ngx_http_groonga_each_loc_conf_in_tree(node->right, callback, user_data);
1096  ngx_http_groonga_each_loc_conf_in_tree(node->tree, callback, user_data);
1097 }
1098 
1099 static void
1100 ngx_http_groonga_each_loc_conf(ngx_http_conf_ctx_t *http_conf,
1102  void *user_data)
1103 {
1104  ngx_http_core_main_conf_t *main_conf;
1105  ngx_http_core_srv_conf_t **server_confs;
1106  ngx_uint_t i;
1107 
1108  if (!http_conf) {
1109  return;
1110  }
1111 
1112  main_conf = http_conf->main_conf[ngx_http_core_module.ctx_index];
1113  server_confs = main_conf->servers.elts;
1114  for (i = 0; i < main_conf->servers.nelts; i++) {
1115  ngx_http_core_srv_conf_t *server_conf;
1116  ngx_http_core_loc_conf_t *location_conf;
1117 
1118  server_conf = server_confs[i];
1119  location_conf = server_conf->ctx->loc_conf[ngx_http_core_module.ctx_index];
1120  ngx_http_groonga_each_loc_conf_in_tree(location_conf->static_locations,
1121  callback,
1122  user_data);
1123  }
1124 }
1125 
1126 static ngx_int_t
1127 ngx_http_groonga_mkdir_p(ngx_log_t *log, const char *dir_name)
1128 {
1129  char sub_path[PATH_MAX];
1130  size_t i, dir_name_length;
1131 
1132  dir_name_length = strlen(dir_name);
1133  sub_path[0] = dir_name[0];
1134  for (i = 1; i < dir_name_length + 1; i++) {
1135  if (dir_name[i] == '/' || dir_name[i] == '\0') {
1136  struct stat stat_buffer;
1137  sub_path[i] = '\0';
1138  if (stat(sub_path, &stat_buffer) == -1) {
1139  if (ngx_create_dir(sub_path, 0700) == -1) {
1140  ngx_log_error(NGX_LOG_EMERG, log, 0,
1141  "failed to create directory: %s (%s): %s",
1142  sub_path, dir_name,
1143  strerror(errno));
1144  return NGX_ERROR;
1145  }
1146  }
1147  }
1148  sub_path[i] = dir_name[i];
1149  }
1150 
1151  return NGX_OK;
1152 }
1153 
1154 static void
1155 ngx_http_groonga_create_database(ngx_http_groonga_loc_conf_t *location_conf,
1157 {
1158  const char *database_base_name;
1159  grn_ctx *context;
1160 
1161  database_base_name = strrchr(location_conf->database_path_cstr, '/');
1162  if (database_base_name) {
1163  char database_dir[PATH_MAX];
1164  database_dir[0] = '\0';
1165  strncat(database_dir,
1166  location_conf->database_path_cstr,
1167  database_base_name - location_conf->database_path_cstr);
1168  data->rc = ngx_http_groonga_mkdir_p(data->log, database_dir);
1169  if (data->rc != NGX_OK) {
1170  return;
1171  }
1172  }
1173 
1174  context = &(location_conf->context);
1175  grn_db_create(context, location_conf->database_path_cstr, NULL);
1176  if (context->rc == GRN_SUCCESS) {
1177  return;
1178  }
1179 
1180  ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1181  "failed to create groonga database: %s",
1182  context->errbuf);
1183  data->rc = NGX_ERROR;
1184 }
1185 
1186 static void
1187 ngx_http_groonga_open_database_callback(ngx_http_groonga_loc_conf_t *location_conf,
1188  void *user_data)
1189 {
1191  grn_ctx *context;
1192 
1193  context = &(location_conf->context);
1194  data->rc = ngx_http_groonga_context_init(context, location_conf,
1195  data->pool, data->log);
1196  if (data->rc != NGX_OK) {
1197  return;
1198  }
1199 
1200  if (!location_conf->database_path.data) {
1201  ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1202  "%s: \"groonga_database\" must be specified in block at %s:%d",
1203  location_conf->name,
1204  location_conf->config_file,
1205  location_conf->config_line);
1206  data->rc = NGX_ERROR;
1207  return;
1208  }
1209 
1210  if (!location_conf->database_path_cstr) {
1211  location_conf->database_path_cstr =
1212  ngx_str_null_terminate(data->pool, &(location_conf->database_path));
1213  }
1214 
1215  grn_db_open(context, location_conf->database_path_cstr);
1216  if (context->rc != GRN_SUCCESS) {
1217  if (location_conf->database_auto_create) {
1218  ngx_http_groonga_create_database(location_conf, data);
1219  } else {
1220  ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1221  "failed to open groonga database: %s",
1222  context->errbuf);
1223  data->rc = NGX_ERROR;
1224  return;
1225  }
1226  }
1227 
1228  location_conf->cache = grn_cache_open(context);
1229  if (!location_conf->cache) {
1230  ngx_log_error(NGX_LOG_EMERG, data->log, 0,
1231  "failed to open groonga cache: %s",
1232  context->errbuf);
1233  data->rc = NGX_ERROR;
1234  return;
1235  }
1236  if (location_conf->cache_limit != NGX_CONF_UNSET_SIZE) {
1238  location_conf->cache,
1239  location_conf->cache_limit);
1240  }
1241 }
1242 
1243 static void
1244 ngx_http_groonga_close_database_callback(ngx_http_groonga_loc_conf_t *location_conf,
1245  void *user_data)
1246 {
1248  grn_ctx *context;
1249 
1250  context = &(location_conf->context);
1251  ngx_http_groonga_context_init_logger(context,
1252  location_conf,
1253  data->pool,
1254  data->log);
1255  ngx_http_groonga_context_init_query_logger(context,
1256  location_conf,
1257  data->pool,
1258  data->log);
1259  grn_cache_current_set(context, location_conf->cache);
1260 
1261  grn_obj_close(context, grn_ctx_db(context));
1262  ngx_http_groonga_context_log_error(data->log, context);
1263 
1264  grn_cache_current_set(context, NULL);
1265  grn_cache_close(context, location_conf->cache);
1266 
1267  grn_ctx_fin(context);
1268 }
1269 
1270 static ngx_int_t
1271 ngx_http_groonga_init_process(ngx_cycle_t *cycle)
1272 {
1273  grn_rc rc;
1274  ngx_http_conf_ctx_t *http_conf;
1276 
1277  rc = grn_init();
1278  if (rc != GRN_SUCCESS) {
1279  return NGX_ERROR;
1280  }
1281 
1283 
1284  http_conf =
1286 
1287  data.log = cycle->log;
1288  data.pool = cycle->pool;
1289  data.rc = NGX_OK;
1290  ngx_http_groonga_each_loc_conf(http_conf,
1291  ngx_http_groonga_open_database_callback,
1292  &data);
1293 
1294  return data.rc;
1295 }
1296 
1297 static void
1298 ngx_http_groonga_exit_process(ngx_cycle_t *cycle)
1299 {
1300  ngx_http_conf_ctx_t *http_conf;
1302 
1303  http_conf =
1305  data.log = cycle->log;
1306  data.pool = cycle->pool;
1307  ngx_http_groonga_each_loc_conf(http_conf,
1308  ngx_http_groonga_close_database_callback,
1309  &data);
1310 
1311  grn_fin();
1312 
1313  return;
1314 }
1315 
1316 /* entry point */
1317 static ngx_command_t ngx_http_groonga_commands[] = {
1318  { ngx_string("groonga"),
1320  ngx_http_groonga_conf_set_groonga_slot,
1322  offsetof(ngx_http_groonga_loc_conf_t, enabled),
1323  NULL },
1324 
1325  { ngx_string("groonga_database"),
1329  offsetof(ngx_http_groonga_loc_conf_t, database_path),
1330  NULL },
1331 
1332  { ngx_string("groonga_database_auto_create"),
1336  offsetof(ngx_http_groonga_loc_conf_t, database_auto_create),
1337  NULL },
1338 
1339  { ngx_string("groonga_base_path"),
1343  offsetof(ngx_http_groonga_loc_conf_t, base_path),
1344  NULL },
1345 
1346  { ngx_string("groonga_log_path"),
1348  ngx_http_groonga_conf_set_log_path_slot,
1350  offsetof(ngx_http_groonga_loc_conf_t, log_path),
1351  NULL },
1352 
1353  { ngx_string("groonga_log_level"),
1355  ngx_http_groonga_conf_set_log_level_slot,
1357  0,
1358  NULL },
1359 
1360  { ngx_string("groonga_query_log_path"),
1362  ngx_http_groonga_conf_set_query_log_path_slot,
1364  offsetof(ngx_http_groonga_loc_conf_t, query_log_path),
1365  NULL },
1366 
1367  { ngx_string("groonga_cache_limit"),
1371  offsetof(ngx_http_groonga_loc_conf_t, cache_limit),
1372  NULL },
1373 
1375 };
1376 
1377 static ngx_http_module_t ngx_http_groonga_module_ctx = {
1378  NULL, /* preconfiguration */
1379  NULL, /* postconfiguration */
1380 
1381  NULL, /* create main configuration */
1382  NULL, /* init main configuration */
1383 
1384  NULL, /* create server configuration */
1385  NULL, /* merge server configuration */
1386 
1387  ngx_http_groonga_create_loc_conf, /* create location configuration */
1388  ngx_http_groonga_merge_loc_conf, /* merge location configuration */
1389 };
1390 
1391 ngx_module_t ngx_http_groonga_module = {
1392  NGX_MODULE_V1,
1393  &ngx_http_groonga_module_ctx, /* module context */
1394  ngx_http_groonga_commands, /* module directives */
1395  NGX_HTTP_MODULE, /* module type */
1396  NULL, /* init master */
1397  NULL, /* init module */
1398  ngx_http_groonga_init_process, /* init process */
1399  NULL, /* init thread */
1400  NULL, /* exit thread */
1401  ngx_http_groonga_exit_process, /* exit process */
1402  NULL, /* exit master */
1404 };