1 /*
2  * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
3  * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
4  * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #ifndef DDEBUG
30 #define DDEBUG 0
31 #endif
32 
33 #include "ngx_postgres_ddebug.h"
34 #include "ngx_postgres_handler.h"
35 #include "ngx_postgres_module.h"
36 #include "ngx_postgres_output.h"
37 #include "ngx_postgres_processor.h"
38 #include "ngx_postgres_util.h"
39 
40 
41 ngx_int_t
ngx_postgres_handler(ngx_http_request_t * r)42 ngx_postgres_handler(ngx_http_request_t *r)
43 {
44     ngx_postgres_loc_conf_t   *pglcf;
45     ngx_postgres_ctx_t        *pgctx;
46     ngx_http_core_loc_conf_t  *clcf;
47     ngx_http_upstream_t       *u;
48     ngx_connection_t          *c;
49     ngx_str_t                  host;
50     ngx_url_t                  url;
51     ngx_int_t                  rc;
52 
53     dd("entering");
54 
55     if (r->subrequest_in_memory) {
56         /* TODO: add support for subrequest in memory by
57          * emitting output into u->buffer instead */
58 
59         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
60                       "postgres: ngx_postgres module does not support"
61                       " subrequests in memory");
62 
63         dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
64         return NGX_HTTP_INTERNAL_SERVER_ERROR;
65     }
66 
67     pglcf = ngx_http_get_module_loc_conf(r, ngx_postgres_module);
68 
69     if ((pglcf->query.def == NULL) && !(pglcf->query.methods_set & r->method)) {
70         if (pglcf->query.methods_set != 0) {
71             dd("returning NGX_HTTP_NOT_ALLOWED");
72             return NGX_HTTP_NOT_ALLOWED;
73         }
74 
75         clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
76 
77         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
78                       "postgres: missing \"postgres_query\" in location \"%V\"",
79                       &clcf->name);
80 
81         dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
82         return NGX_HTTP_INTERNAL_SERVER_ERROR;
83     }
84 
85     rc = ngx_http_discard_request_body(r);
86     if (rc != NGX_OK) {
87         dd("returning rc:%d", (int) rc);
88         return rc;
89     }
90 
91 #if defined(nginx_version) \
92     && (((nginx_version >= 7063) && (nginx_version < 8000)) \
93         || (nginx_version >= 8007))
94 
95     if (ngx_http_upstream_create(r) != NGX_OK) {
96         dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
97         return NGX_HTTP_INTERNAL_SERVER_ERROR;
98     }
99 
100     u = r->upstream;
101 
102 #else /* 0.7.x < 0.7.63, 0.8.x < 0.8.7 */
103 
104     u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
105     if (u == NULL) {
106         dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
107         return NGX_HTTP_INTERNAL_SERVER_ERROR;
108     }
109 
110     u->peer.log = r->connection->log;
111     u->peer.log_error = NGX_ERROR_ERR;
112 #  if (NGX_THREADS)
113     u->peer.lock = &r->connection->lock;
114 #  endif
115     r->upstream = u;
116 #endif
117 
118     if (pglcf->upstream_cv) {
119         /* use complex value */
120         if (ngx_http_complex_value(r, pglcf->upstream_cv, &host) != NGX_OK) {
121             dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
122             return NGX_HTTP_INTERNAL_SERVER_ERROR;
123         }
124 
125         if (host.len == 0) {
126             clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
127 
128             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
129                           "postgres: empty \"postgres_pass\" (was: \"%V\")"
130                           " in location \"%V\"", &pglcf->upstream_cv->value,
131                           &clcf->name);
132 
133             dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
134             return NGX_HTTP_INTERNAL_SERVER_ERROR;
135         }
136 
137         ngx_memzero(&url, sizeof(ngx_url_t));
138 
139         url.host = host;
140         url.no_resolve = 1;
141 
142         pglcf->upstream.upstream = ngx_postgres_find_upstream(r, &url);
143         if (pglcf->upstream.upstream == NULL) {
144             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
145                           "postgres: upstream name \"%V\" not found", &host);
146 
147             dd("returning NGX_ERROR");
148             return NGX_HTTP_INTERNAL_SERVER_ERROR;
149         }
150     }
151 
152     pgctx = ngx_pcalloc(r->pool, sizeof(ngx_postgres_ctx_t));
153     if (pgctx == NULL) {
154         dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
155         return NGX_HTTP_INTERNAL_SERVER_ERROR;
156     }
157 
158     /*
159      * set by ngx_pcalloc():
160      *
161      *     pgctx->response = NULL
162      *     pgctx->var_query = { 0, NULL }
163      *     pgctx->variables = NULL
164      *     pgctx->status = 0
165      */
166 
167     pgctx->var_cols = NGX_ERROR;
168     pgctx->var_rows = NGX_ERROR;
169     pgctx->var_affected = NGX_ERROR;
170 
171     if (pglcf->variables != NULL) {
172         pgctx->variables = ngx_array_create(r->pool, pglcf->variables->nelts,
173                                             sizeof(ngx_str_t));
174         if (pgctx->variables == NULL) {
175             dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
176             return NGX_HTTP_INTERNAL_SERVER_ERROR;
177         }
178 
179         /* fake ngx_array_push'ing */
180         pgctx->variables->nelts = pglcf->variables->nelts;
181 
182         ngx_memzero(pgctx->variables->elts,
183                     pgctx->variables->nelts * pgctx->variables->size);
184     }
185 
186     ngx_http_set_ctx(r, pgctx, ngx_postgres_module);
187 
188     u->schema.len = sizeof("postgres://") - 1;
189     u->schema.data = (u_char *) "postgres://";
190 
191     u->output.tag = (ngx_buf_tag_t) &ngx_postgres_module;
192 
193     u->conf = &pglcf->upstream;
194 
195     u->create_request = ngx_postgres_create_request;
196     u->reinit_request = ngx_postgres_reinit_request;
197     u->process_header = ngx_postgres_process_header;
198     u->abort_request = ngx_postgres_abort_request;
199     u->finalize_request = ngx_postgres_finalize_request;
200 
201     /* we bypass the upstream input filter mechanism in
202      * ngx_http_upstream_process_headers */
203 
204     u->input_filter_init = ngx_postgres_input_filter_init;
205     u->input_filter = ngx_postgres_input_filter;
206     u->input_filter_ctx = NULL;
207 
208 #if defined(nginx_version) && (nginx_version >= 8011)
209     r->main->count++;
210 #endif
211 
212     ngx_http_upstream_init(r);
213 
214     /* override the read/write event handler to our own */
215     u->write_event_handler = ngx_postgres_wev_handler;
216     u->read_event_handler = ngx_postgres_rev_handler;
217 
218     /* a bit hack-ish way to return error response (clean-up part) */
219     if ((u->peer.connection) && (u->peer.connection->fd == 0)) {
220         c = u->peer.connection;
221         u->peer.connection = NULL;
222 
223         if (c->write->timer_set) {
224             ngx_del_timer(c->write);
225         }
226 
227 #if defined(nginx_version) && (nginx_version >= 1001004)
228         if (c->pool) {
229             ngx_destroy_pool(c->pool);
230         }
231 #endif
232 
233         ngx_free_connection(c);
234 
235         ngx_postgres_upstream_finalize_request(r, u,
236 #if defined(nginx_version) && (nginx_version >= 8017)
237                                                NGX_HTTP_SERVICE_UNAVAILABLE);
238 #else
239             pgctx->status ? pgctx->status : NGX_HTTP_INTERNAL_SERVER_ERROR);
240 #endif
241     }
242 
243     dd("returning NGX_DONE");
244     return NGX_DONE;
245 }
246 
247 void
ngx_postgres_wev_handler(ngx_http_request_t * r,ngx_http_upstream_t * u)248 ngx_postgres_wev_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
249 {
250     ngx_connection_t  *pgxc;
251 
252     dd("entering");
253 
254     /* just to ensure u->reinit_request always gets called for
255      * upstream_next */
256     u->request_sent = 1;
257 
258     pgxc = u->peer.connection;
259 
260     if (pgxc->write->timedout) {
261         dd("postgres connection write timeout");
262 
263         ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
264 
265         dd("returning");
266         return;
267     }
268 
269     if (ngx_postgres_upstream_test_connect(pgxc) != NGX_OK) {
270         dd("postgres connection is broken");
271 
272         ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
273 
274         dd("returning");
275         return;
276     }
277 
278     ngx_postgres_process_events(r);
279 
280     dd("returning");
281 }
282 
283 void
ngx_postgres_rev_handler(ngx_http_request_t * r,ngx_http_upstream_t * u)284 ngx_postgres_rev_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
285 {
286     ngx_connection_t  *pgxc;
287 
288     dd("entering");
289 
290     /* just to ensure u->reinit_request always gets called for
291      * upstream_next */
292     u->request_sent = 1;
293 
294     pgxc = u->peer.connection;
295 
296     if (pgxc->read->timedout) {
297         dd("postgres connection read timeout");
298 
299         ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
300 
301         dd("returning");
302         return;
303     }
304 
305     if (ngx_postgres_upstream_test_connect(pgxc) != NGX_OK) {
306         dd("postgres connection is broken");
307 
308         ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
309 
310         dd("returning");
311         return;
312     }
313 
314     ngx_postgres_process_events(r);
315 
316     dd("returning");
317 }
318 
319 ngx_int_t
ngx_postgres_create_request(ngx_http_request_t * r)320 ngx_postgres_create_request(ngx_http_request_t *r)
321 {
322     dd("entering");
323 
324     r->upstream->request_bufs = NULL;
325 
326     dd("returning NGX_OK");
327     return NGX_OK;
328 }
329 
330 ngx_int_t
ngx_postgres_reinit_request(ngx_http_request_t * r)331 ngx_postgres_reinit_request(ngx_http_request_t *r)
332 {
333     ngx_http_upstream_t  *u;
334 
335     dd("entering");
336 
337     u = r->upstream;
338 
339     /* override the read/write event handler to our own */
340     u->write_event_handler = ngx_postgres_wev_handler;
341     u->read_event_handler = ngx_postgres_rev_handler;
342 
343     dd("returning NGX_OK");
344     return NGX_OK;
345 }
346 
347 void
ngx_postgres_abort_request(ngx_http_request_t * r)348 ngx_postgres_abort_request(ngx_http_request_t *r)
349 {
350     dd("entering & returning (dummy function)");
351 }
352 
353 void
ngx_postgres_finalize_request(ngx_http_request_t * r,ngx_int_t rc)354 ngx_postgres_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
355 {
356     ngx_postgres_ctx_t  *pgctx;
357 
358     dd("entering");
359 
360     if (rc == NGX_OK) {
361         pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
362 
363         ngx_postgres_output_chain(r, pgctx->response);
364     }
365 
366     dd("returning");
367 }
368 
369 ngx_int_t
ngx_postgres_process_header(ngx_http_request_t * r)370 ngx_postgres_process_header(ngx_http_request_t *r)
371 {
372     dd("entering");
373 
374     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
375                   "postgres: ngx_postgres_process_header should not"
376                   " be called by the upstream");
377 
378     dd("returning NGX_ERROR");
379     return NGX_ERROR;
380 }
381 
382 ngx_int_t
ngx_postgres_input_filter_init(void * data)383 ngx_postgres_input_filter_init(void *data)
384 {
385     ngx_http_request_t  *r = data;
386 
387     dd("entering");
388 
389     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
390                   "postgres: ngx_postgres_input_filter_init should not"
391                   " be called by the upstream");
392 
393     dd("returning NGX_ERROR");
394     return NGX_ERROR;
395 }
396 
397 ngx_int_t
ngx_postgres_input_filter(void * data,ssize_t bytes)398 ngx_postgres_input_filter(void *data, ssize_t bytes)
399 {
400     ngx_http_request_t  *r = data;
401 
402     dd("entering");
403 
404     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
405                   "postgres: ngx_postgres_input_filter should not"
406                   " be called by the upstream");
407 
408     dd("returning NGX_ERROR");
409     return NGX_ERROR;
410 }
411