├── .gitignore
├── Makefile
├── README.markdown
├── apiary.apib
├── config
├── doc
├── couchbase.en.markdown
└── couchbase.ru.markdown
├── etc
├── nginx.conf
└── nginx.stress.conf
├── src
├── ddebug.h
├── ngx_lcb_callbacks.c
├── ngx_lcb_callbacks.h
├── ngx_lcb_module.c
├── ngx_lcb_module.h
├── ngx_lcb_plugin.c
└── ngx_lcb_plugin.h
└── t
└── smoke.t
/.gitignore:
--------------------------------------------------------------------------------
1 | *.orig
2 | cbgb*
3 | t/servroot
4 | tmp
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GET := wget -q -O
2 | GUNZIP := gzip -d
3 | PLATFORM = $(shell uname -sm)
4 | MOCK = ./cbgb
5 | PKGNAME := nginx-couchbase-module-$(shell git describe)
6 |
7 | NGX_VERSIONS := v1.2.6 v1.4.0
8 | AB_CONCURRENCY := 10
9 | AB_REQUESTS := 1000
10 | AB_NGX_CONFIG := etc/nginx.stress.conf
11 | AB_URI := http://localhost:8080/lcb?cmd=set&key=foo&val=bar
12 |
13 | all:
14 | $(MAKE) -C.. all
15 |
16 | check:
17 | @for ver in ${NGX_VERSIONS} ; do \
18 | echo "==========================" ; \
19 | echo "Checking with nginx $$ver" ; \
20 | if (cd ../nginx; git checkout $$ver && git clean -dfx) && \
21 | $(MAKE) -C.. check ; \
22 | then \
23 | res="$$res\n$$ver - OK" ; \
24 | else \
25 | res="$$res\n$$ver - FAILURE" ; \
26 | fi \
27 | done; \
28 | printf "$$res\n"
29 |
30 | stress:
31 | @for ver in ${NGX_VERSIONS} ; do \
32 | echo "==========================" ; \
33 | echo "Stressing with nginx $$ver" ; \
34 | pid=`cat ../install/logs/nginx.pid 2>/dev/null` ; \
35 | if [ -n $$pid ] ; then \
36 | kill $$pid > /dev/null 2>&1 ; \
37 | fi ; \
38 | if (cd ../nginx; git checkout $$ver && git clean -dfx) && \
39 | $(MAKE) -C.. all WARNINGS= DEBUG=0 && \
40 | cp $(AB_NGX_CONFIG) ../install/conf/nginx.conf && \
41 | ../install/sbin/nginx && \
42 | ab -c $(AB_CONCURRENCY) -n $(AB_REQUESTS) '$(AB_URI)' ; \
43 | then \
44 | res="$$res\n$$ver - OK" ; \
45 | else \
46 | res="$$res\n$$ver - FAILURE" ; \
47 | fi ; \
48 | pid=`cat ../install/logs/nginx.pid 2>/dev/null` ; \
49 | if [ -n $$pid ] ; then \
50 | kill $$pid > /dev/null 2>&1 ; \
51 | fi ; \
52 | done; \
53 | printf "$$res\n"
54 |
55 | cbgb-run: cbgb
56 | $(MOCK) > $(MOCK).log 2>&1 &
57 | @echo wait 1 seconds until cbgb will start
58 | @sleep 1
59 |
60 | cbgb:
61 | ifeq ($(PLATFORM),Linux x86_64)
62 | $(GET) $(MOCK).gz http://cbgb.io/cbgb.lin64.gz
63 | else
64 | @echo "Unknown OS. Update cbgb target in Makefile"
65 | @exit 1
66 | endif
67 | $(GUNZIP) $(MOCK).gz
68 | chmod a+x $(MOCK)
69 |
70 | dist:
71 | git clean -dfx
72 | mkdir $(PKGNAME)
73 | cp -a config src etc doc README.markdown t $(PKGNAME)
74 | tar cf - $(PKGNAME) | gzip -9 - > $(PKGNAME).tar.gz
75 | rm -rf $(PKGNAME)
76 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # nginx-couchbase-module
2 |
3 | Hi, this is couchbase module for nginx. Here is short build
4 | instructions. More docs are coming.
5 |
6 | Your test machine should have C compiler installed, as well as
7 | autotools.
8 |
9 | mkdir couchbase-nginx-module
10 | cd couchbase-nginx-module
11 | repo init -u git://github.com/trondn/manifests.git -m nginx.xml
12 | repo sync
13 | make
14 |
15 | # Important Note
16 |
17 | This release depends on [fork of libcouchbase][1], therefore it **will
18 | not work** with the client, you can download from binary repositories.
19 | The most recent version of the forked library is **2.0.3nginx3** and
20 | you can download it using this URL:
21 | http://packages.couchbase.com/clients/c/libcouchbase-2.0.3nginx3.tar.gz
22 |
23 | The changes from the fork are going to be integrated to upstream soon.
24 |
25 | # Testing
26 |
27 | There is way to run all tests against different versions of nginx, in
28 | case you are using repo to checkout sources:
29 |
30 | cd module
31 | make check NGX_VERSIONS="v1.4.0 v1.3.10"
32 |
33 | By default `make check` will run use `NGX_VERSIONS="v1.2.6 v1.4.0"`.
34 |
35 | Note that until version [v1.3.10](https://github.com/nginx/nginx/commit/v1.3.10)
36 | nginx has a bug which doesn't allow to add headers to 201 responses,
37 | therefore corresponding tests are skipped in the testsuite for these
38 | versions.
39 |
40 | There is also make target for stress testing using Apache Benchmark:
41 | `make stress`. It also honours `NGX_VERSIONS` and also depends on
42 | several more variables. These are default values, pretty
43 | self-explanatory:
44 |
45 | NGX_VERSIONS="v1.2.6 v1.4.0"
46 | AB_CONCURRENCY=10
47 | AB_REQUESTS=1000
48 | AB_NGX_CONFIG=etc/nginx.stress.conf
49 | AB_URI=http://localhost:8080/lcb?cmd=set&key=foo&val=bar
50 |
51 | # Usage
52 |
53 | This section describes step by step guide how to build nginx with
54 | couchbase module. This version (0.3.1) is only tested with
55 | nginx v1.3.7, v1.2.6 and v1.4.0, other versions might also work, more
56 | broad coverage is subject of upcoming releases. Also it requires
57 | modified libcouchbase library from `nginx` branch of my fork
58 | [avsej/libcouchbase][1].
59 |
60 | Create build directory and download all dependencies there:
61 |
62 | mkdir nginx-couchbase
63 | cd nginx-couchbase
64 | wget http://nginx.org/download/nginx-1.3.7.tar.gz
65 | wget http://packages.couchbase.com/clients/c/libcouchbase-2.0.3nginx3.tar.gz
66 | wget http://packages.couchbase.com/clients/c/nginx-couchbase-module-0.3.1.tar.gz
67 | for i in *.tar.gz; do tar xvf $i; done
68 |
69 | Build and install everything into `/opt/nginx-couchbase` prefix:
70 |
71 | export PREFIX=/opt/nginx-couchbase
72 |
73 | cd libcouchbase-2.0.3nginx3
74 | ./configure --prefix=$PREFIX --enable-debug --disable-plugins --disable-tests --disable-couchbasemock
75 | make && sudo make install
76 | cd ..
77 |
78 | cd nginx-1.3.7
79 | export LIBCOUCHBASE_INCLUDE=$PREFIX/include
80 | export LIBCOUCHBASE_LIB=$PREFIX/lib
81 | ./configure --prefix=$PREFIX --with-debug --add-module=../nginx-couchbase-module-0.3.1
82 | make && sudo make install
83 |
84 | Now you should install and configure Couchbase Server. Read more at
85 | official site: http://www.couchbase.com/download. Make sure you have
86 | configured bucket `default`.
87 |
88 | Given all stuff installed, lets deploy the simplest config file,
89 | which can be find in `etc/nginx.conf` of this repository. Here is
90 | `server` section of this config:
91 |
92 | server {
93 | listen 8080;
94 | server_name localhost;
95 | location /cb {
96 | set $couchbase_key $arg_key;
97 | set $couchbase_cmd $arg_cmd;
98 | set $couchbase_val $arg_val;
99 | couchbase_connect_timeout 2ms;
100 | couchbase_timeout 1.5ms;
101 | couchbase_pass localhost:8091,127.0.0.1:8091,localhost bucket=default;
102 | }
103 | }
104 |
105 | To deploy and check config file validness, run this commands (assuming
106 | your current directory is the root of this repository).
107 |
108 | sudo cp etc/nginx.conf /opt/nginx-couchbase/conf/nginx.conf
109 | sudo /opt/nginx-couchbase/sbin/nginx -t
110 |
111 | It should report that config is valid and everything is ok. Lets start
112 | the server and play with it using `curl`.
113 |
114 | sudo /opt/nginx-couchbase/sbin/nginx
115 |
116 | Put key into the storage
117 |
118 | $ curl -v 'http://localhost:8080/cb?cmd=set&key=foo&val=bar'
119 | * About to connect() to localhost port 8080 (#0)
120 | * Trying 127.0.0.1...
121 | * Connected to localhost (127.0.0.1) port 8080 (#0)
122 | > GET /cb?cmd=set&key=foo&val=bar HTTP/1.1
123 | > User-Agent: curl/7.29.0
124 | > Host: localhost:8080
125 | > Accept: */*
126 | >
127 | < HTTP/1.1 201 Created
128 | < Server: nginx/1.3.7
129 | < Date: Wed, 17 Apr 2013 23:47:06 GMT
130 | < Content-Length: 0
131 | < Connection: keep-alive
132 | < X-Couchbase-CAS: 16808439146359685120
133 | <
134 | * Connection #0 to host localhost left intact
135 |
136 | Fetch document back
137 |
138 | $ curl -v 'http://localhost:8080/cb?cmd=get&key=foo'
139 | * About to connect() to localhost port 8080 (#0)
140 | * Trying 127.0.0.1...
141 | * Connected to localhost (127.0.0.1) port 8080 (#0)
142 | > GET /cb?cmd=get&key=foo HTTP/1.1
143 | > User-Agent: curl/7.29.0
144 | > Host: localhost:8080
145 | > Accept: */*
146 | >
147 | < HTTP/1.1 200 OK
148 | < Server: nginx/1.3.7
149 | < Date: Wed, 17 Apr 2013 23:48:16 GMT
150 | < Content-Length: 3
151 | < Connection: keep-alive
152 | < X-Couchbase-CAS: 11078484393219129344
153 | <
154 | * Connection #0 to host localhost left intact
155 |
156 | Delete document from the storage
157 |
158 | $ curl -v 'http://localhost:8080/cb?cmd=delete&key=foo'
159 | * About to connect() to localhost port 8080 (#0)
160 | * Trying 127.0.0.1...
161 | * Connected to localhost (127.0.0.1) port 8080 (#0)
162 | > GET /cb?cmd=delete&key=foo HTTP/1.1
163 | > User-Agent: curl/7.29.0
164 | > Host: localhost:8080
165 | > Accept: */*
166 | >
167 | < HTTP/1.1 200 OK
168 | < Server: nginx/1.3.7
169 | < Date: Wed, 17 Apr 2013 23:48:49 GMT
170 | < Content-Length: 0
171 | < Connection: keep-alive
172 | < X-Couchbase-CAS: 11150541987257057280
173 | <
174 | * Connection #0 to host localhost left intact
175 |
176 | If you will skip `$couchbase_key`, `$couchbase_cmd`, `$couchbase_val`
177 | declarations it will use URL, HTTP method and HTTP body
178 | correspondingly.
179 |
180 | [1]: https://github.com/avsej/libcouchbase/tree/nginx
181 |
--------------------------------------------------------------------------------
/apiary.apib:
--------------------------------------------------------------------------------
1 | HOST: http://example.com/
2 |
3 | --- Couchbase NGINX Module ---
4 |
5 | ---
6 | This document describes the REST API exposed to access Couchbase
7 | resources
8 |
9 | There are several terms we are using while describing this API.
10 |
11 |
12 |
13 | Term |
14 | Definition |
15 |
16 |
17 | document |
18 | Arbitrary JSON or BLOB value. It could be referenced by
19 | its ID |
20 |
21 |
22 | bucket |
23 | The logical storage, which contains the documents. Like
24 | database in SQL world, or
25 | collection in mongodb |
26 |
27 |
28 |
29 | Lets assume we have a bucket named `wonderland`.
30 | ---
31 |
32 | --
33 | Document CRUD
34 | This section shows how to perform simple Create, Retrieve, Update and
35 | Delete operations
36 | --
37 |
38 | Create document in the bucket
39 | POST /wonderland
40 | > Content-Type: application/json
41 | {"name":"Alice","species":"human"}
42 | < 201
43 | < ETag: "f5b8da2cf1560000"
44 | < Location: http://couchbasenginx.apiary.io/wonderland/0a4683e4b4b161f505d0c2ae0c3a1abf
45 |
46 |
47 | You can also specify ID with POST request if you need to `create` the
48 | document with given ID.
49 | POST /wonderland/alice
50 | > Content-Type: application/json
51 | {"name":"Alice","species":"human"}
52 | < 201
53 | < ETag: "f5b8da2cf1570000"
54 | < Location: http://couchbasenginx.apiary.io/wonderland/alice
55 |
56 |
57 | Note that when you are using POST request, the server is always
58 | supposed to create the new document. This means that you will get an
59 | error if the document already exists.
60 | POST /wonderland/foobar
61 | > Content-Type: application/json
62 | {"foo":"bar"}
63 | < 409
64 | < Content-Type: application/json
65 | < Content-Length: 74
66 | {"error":"key_eexists","reason":"Key exists (with a different ETag value)"}
67 |
68 |
69 | Update the document
70 | PUT /wonderland/white-rabbit
71 | > Content-Type: application/json
72 | {"name":"Write Rabbit","species":"rabbit"}
73 | < 204
74 | < ETag: "3d24e52cf1570000"
75 | < Location: http://couchbasenginx.apiary.io/wonderland/white-rabbit
76 |
77 |
78 | You can also specify `If-Match` header and the server will perform
79 | optimistic lock before updating. This operation might fail `412
80 | Precondition Failed`
81 | PUT /wonderland/duchess
82 | > Content-Type: application/json
83 | > If-Match: "wrong_etag"
84 | {"name":"Duchess","species":"human"}
85 | < 412
86 | < Content-Type: application/json
87 | < Content-Length: 74
88 | {"error":"key_eexists","reason":"Key exists (with a different ETag value)"}
89 |
90 |
91 | If the document being updated doesn't exist, you will get `404 Not
92 | Found` error
93 | PUT /wonderland/sheep
94 | {"name":"Sheep","species":"sheep"}
95 | < 404
96 | < Content-Type: application/json
97 | < Content-Length: 45
98 | {"error":"key_enoent","reason":"No such key"}
99 |
100 |
101 | Removing document from the bucket
102 | DELETE /wonderland/caterpillar
103 | < 204
104 | < ETag: "7f463c2df1570000"
105 |
106 |
107 | You can also specify `If-Match` header if you need to ensure you are removing
108 | know version of the document.
109 | DELETE /wonderland/bill-the-lizard
110 | > If-Match: "wrong_etag"
111 | < 412
112 | < Content-Type: application/json
113 | < Content-Length: 74
114 | {"error":"key_eexists","reason":"Key exists (with a different ETag value)"}
115 |
116 |
117 | If the document being removed doesn't exist, you will get `404 Not
118 | Found` error
119 | DELETE /wonderland/jabberwock
120 | < 404
121 | < Content-Type: application/json
122 | < Content-Length: 45
123 | {"error":"key_enoent","reason":"No such key"}
124 |
125 |
126 | Getting the document by ID.
127 | GET /wonderland/hatter
128 | < 200
129 | < Content-Type: application/json
130 | < Content-Length: 36
131 | < ETag: "5d45c301fc570000"
132 | {"name":"Hatter","species":"human"}
133 |
134 |
135 | In case when the given document ID isn't exist in the bucket you will
136 | get `404 Not Found`
137 | GET /wonderland/red-queen
138 | < 404
139 | < Content-Type: application/json
140 | < Content-Length: 45
141 | {"error":"key_enoent","reason":"No such key"}
142 |
143 |
144 | Use `HEAD` request to skip the document body
145 | HEAD /wonderland/gryphon
146 | < 200
147 | < Content-Type: application/json
148 | < ETag: "55c3a92df1570000"
149 |
150 |
151 | Each operation is carry ETag, which internally is CAS value. The ETag
152 | value can be used in the conditional requests with `If-Match`,
153 | `If-None-Match`. See examples of `If-Match` above.
154 |
155 | Check if the key has been modified (in this case the server responds
156 | that it wasn't modified):
157 | HEAD /wonderland/dormouse
158 | > If-None-Match: "3f5b792df1570000"
159 | < 304
160 |
161 |
162 | --
163 | Couchbase Views
164 | This section shows more advanced server feature: View indexes
165 | --
166 |
167 | One of the features of the Couchbase is ability to build efficient
168 | indexes leveraging Map/Reduce. They are called Views and you can
169 | define them on Admin Console UI.
170 |
171 | This module allows you to query your views proxying them to Couchbase.
172 | All arguments will be transparently passed to Couchbase and the result
173 | will be streamed back.
174 |
175 | For example we have a view `all` defined in the design document
176 | `characters`. It is simple map which will just emit all known
177 | characters (without any reduce function):
178 |
179 | function (doc, meta) {
180 | emit(meta.id, null);
181 | }
182 |
183 | The result will look like (note: when `id` isn't accessible like for
184 | reduced views, the `meta` will be missing too)
185 | GET /wonderland/_design/characters/_view/all
186 | < 200
187 | < Transfer-Encoding: chunked
188 | < Content-Type: application/json
189 | {"total_rows":20,"rows":[
190 | {"id":"alice","key":"alice","value":null,"meta":{"etag":"f5b8da2cf1570000"}},
191 | {"id":"bill-the-lizard","key":"bill-the-lizard","value":null,"meta":{"etag":"accb302df1570000"}},
192 | {"id":"caterpillar","key":"caterpillar","value":null,"meta":{"etag":"7f463c2df1570000"}},
193 | {"id":"cheshire-cat","key":"cheshire-cat","value":null,"meta":{"etag":"5b99582df1570000"}},
194 | {"id":"dodo","key":"dodo","value":null,"meta":{"etag":"2fe2ed2cf1570000"}},
195 | {"id":"dormouse","key":"dormouse","value":null,"meta":{"etag":"3f5b792df1570000"}},
196 | {"id":"duchess","key":"duchess","value":null,"meta":{"etag":"a8bc492df1570000"}},
197 | {"id":"duck","key":"duck","value":null,"meta":{"etag":"e076132df1570000"}},
198 | {"id":"eaglet","key":"eaglet","value":null,"meta":{"etag":"b9bd062df1570000"}},
199 | {"id":"gryphon","key":"gryphon","value":null,"meta":{"etag":"55c3a92df1570000"}},
200 | {"id":"hatter","key":"hatter","value":null,"meta":{"etag":"5d45c301fc570000"}},
201 | {"id":"king-of-hearts","key":"king-of-hearts","value":null,"meta":{"etag":"4e1c9f2df1570000"}},
202 | {"id":"knave-of-hearts","key":"knave-of-hearts","value":null,"meta":{"etag":"43fa922df1570000"}},
203 | {"id":"lory","key":"lory","value":null,"meta":{"etag":"01f6fa2cf1570000"}},
204 | {"id":"march-hare","key":"march-hare","value":null,"meta":{"etag":"08f6642df1570000"}},
205 | {"id":"mock-turtle","key":"mock-turtle","value":null,"meta":{"etag":"11a8b52df1570000"}},
206 | {"id":"mouse","key":"mouse","value":null,"meta":{"etag":"fa38ea2cf1570000"}},
207 | {"id":"pat","key":"pat","value":null,"meta":{"etag":"8bd71f2df1570000"}},
208 | {"id":"queen-of-hearts","key":"queen-of-hearts","value":null,"meta":{"etag":"f72c862df1570000"}},
209 | {"id":"white-rabbit","key":"white-rabbit","value":null,"meta":{"etag":"3d24e52cf1570000"}},
210 | ]
211 | }
212 |
213 |
214 | You can pass the any of supported query parameters, like
215 | `include_docs=true` for example:
216 | GET /wonderland/_design/characters/_view/all?include_docs=true
217 | < 200
218 | < Transfer-Encoding: chunked
219 | < Content-Type: application/json
220 | {"total_rows":20,"rows":[
221 | {"id":"alice","key":"alice","value":null,"meta":{"etag":"f5b8da2cf1570000"},"json":{"name":"Alice","species":"human"}},
222 | {"id":"bill-the-lizard","key":"bill-the-lizard","value":null,"meta":{"etag":"accb302df1570000"},"json":{"name":"Bill the Lizard","species":"lizard"}},
223 | {"id":"caterpillar","key":"caterpillar","value":null,"meta":{"etag":"7f463c2df1570000"},"json":{"name":"Caterpillar","species":"caterpillar"}},
224 | {"id":"cheshire-cat","key":"cheshire-cat","value":null,"meta":{"etag":"5b99582df1570000"},"json":{"name":"Cheshire Cat","species":"cat"}},
225 | {"id":"dodo","key":"dodo","value":null,"meta":{"etag":"2fe2ed2cf1570000"},"json":{"name":"Dodo","species":"bird"}},
226 | {"id":"dormouse","key":"dormouse","value":null,"meta":{"etag":"3f5b792df1570000"},"json":{"name":"Dormouse","species":"dormouse"}},
227 | {"id":"duchess","key":"duchess","value":null,"meta":{"etag":"a8bc492df1570000"},"json":{"name":"Duchess","species":"human"}},
228 | {"id":"duck","key":"duck","value":null,"meta":{"etag":"e076132df1570000"},"json":{"name":"Duck","species":"bird"}},
229 | {"id":"eaglet","key":"eaglet","value":null,"meta":{"etag":"b9bd062df1570000"},"json":{"name":"Eaglet","species":"bird"}},
230 | {"id":"gryphon","key":"gryphon","value":null,"meta":{"etag":"55c3a92df1570000"},"json":{"name":"Gryphon","species":"gryphon"}},
231 | {"id":"hatter","key":"hatter","value":null,"meta":{"etag":"5d45c301fc570000"},"json":"foo"},
232 | {"id":"king-of-hearts","key":"king-of-hearts","value":null,"meta":{"etag":"4e1c9f2df1570000"},"json":{"name":"King of Hearts","species":"card"}},
233 | {"id":"knave-of-hearts","key":"knave-of-hearts","value":null,"meta":{"etag":"43fa922df1570000"},"json":{"name":"Knave of Hearts","species":"card"}},
234 | {"id":"lory","key":"lory","value":null,"meta":{"etag":"01f6fa2cf1570000"},"json":{"name":"Lory","species":"bird"}},
235 | {"id":"march-hare","key":"march-hare","value":null,"meta":{"etag":"08f6642df1570000"},"json":{"name":"March Hare","species":"hare"}},
236 | {"id":"mock-turtle","key":"mock-turtle","value":null,"meta":{"etag":"11a8b52df1570000"},"json":{"name":"Mock Turtle","species":"turtle"}},
237 | {"id":"mouse","key":"mouse","value":null,"meta":{"etag":"fa38ea2cf1570000"},"json":{"name":"Mouse","species":"mouse"}},
238 | {"id":"pat","key":"pat","value":null,"meta":{"etag":"8bd71f2df1570000"},"json":{"name":"Pat","species":"pig"}},
239 | {"id":"queen-of-hearts","key":"queen-of-hearts","value":null,"meta":{"etag":"f72c862df1570000"},"json":{"name":"Queen of Hearts","species":"card"}},
240 | {"id":"white-rabbit","key":"white-rabbit","value":null,"meta":{"etag":"3d24e52cf1570000"},"json":{"name":"White Rabbit","species":"rabbit"}}
241 | ]
242 | }
243 |
--------------------------------------------------------------------------------
/config:
--------------------------------------------------------------------------------
1 | # vim:filetype=sh
2 |
3 | ngx_type="uint32_t" . auto/types/sizeof
4 | ngx_param=NGX_MAX_UINT32_T_VALUE; ngx_value=$ngx_max_value; . auto/types/value
5 | ngx_param=NGX_UINT32_T_LEN; ngx_value=$ngx_max_len; . auto/types/value
6 |
7 | ngx_type="uint64_t" . auto/types/sizeof
8 | ngx_param=NGX_MAX_UINT64_T_VALUE; ngx_value=$ngx_max_value; . auto/types/value
9 | ngx_param=NGX_UINT64_T_LEN; ngx_value=$ngx_max_len; . auto/types/value
10 |
11 | ngx_feature_name=
12 | ngx_feature_run=no
13 | ngx_feature_incs="#include "
14 | ngx_feature_test="lcb_get_version(NULL)"
15 |
16 | if [ -n "$LIBCOUCHBASE_INCLUDE" -o -n "$LIBCOUCHBASE_LIB" ]; then
17 | # explicit set libcouchbase lib path
18 | ngx_feature="libcouchbase library in directories specified by LIBCOUCHBASE_INCLUDE ($LIBCOUCHBASE_INCLUDE) and LIBCOUCHBASE_LIB ($LIBCOUCHBASE_LIB)"
19 | ngx_feature_path="$LIBCOUCHBASE_INCLUDE"
20 | if [ $NGX_RPATH = YES ]; then
21 | ngx_feature_libs="-R$LIBCOUCHBASE_LIB -L$LIBCOUCHBASE_LIB -lcouchbase"
22 | else
23 | ngx_feature_libs="-Wl,-rpath,$LIBCOUCHBASE_LIB -L$LIBCOUCHBASE_LIB -lcouchbase"
24 | fi
25 | . auto/feature
26 | else
27 | # autodicovery
28 | ngx_feature="libcouchbase library"
29 | ngx_feature_path=
30 | ngx_feature_libs="-lcouchbase"
31 | . auto/feature
32 | fi
33 |
34 | if [ $ngx_found = yes ]; then
35 | CORE_INCS="$CORE_INCS $ngx_feature_path"
36 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
37 | else
38 | cat << END
39 | $0: error: the ngx_http_couchbase_module requires the libcouchbase library.
40 | END
41 | exit 1
42 | fi
43 |
44 | echo "CORE_INCS = $CORE_INCS"
45 | echo "CORE_LIBS = $CORE_LIBS"
46 |
47 | ngx_addon_name=ngx_http_couchbase_module
48 | HTTP_MODULES="$HTTP_MODULES ngx_http_couchbase_module"
49 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_lcb_plugin.c $ngx_addon_dir/src/ngx_lcb_callbacks.c $ngx_addon_dir/src/ngx_lcb_module.c"
50 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_lcb_module.h $ngx_addon_dir/src/ngx_lcb_callbacks.h"
51 |
52 | have=NGX_COUCHBASE_MODULE . auto/have
53 |
--------------------------------------------------------------------------------
/doc/couchbase.en.markdown:
--------------------------------------------------------------------------------
1 | # Nginx Couchbase Module (0.3.1)
2 |
3 | [Couchbase][1] client for [nginx][2].
4 |
5 | ## Description
6 |
7 | This module translates requests to Couchbase cluster, specified with
8 | directive [couchbase_pass][3], and returns result back to the client.
9 |
10 | By default, the module can use information, accessible in the request,
11 | to construct data packet. As a key it use part of the request URI,
12 | which remains after stripping the name of the matching location.
13 |
14 | location /cache/ {
15 | couchbase_pass;
16 | }
17 |
18 | In example above, if address `/cache/example.html` will be requested
19 | from the server, then the key will be `example.html`.
20 |
21 | To choose command, which will be sent to Couchbase Server, it use HTTP
22 | method. The table below shows correspondence commands to the methods.
23 |
24 |
25 | HTTP | Couchbase |
26 | GET, HEAD | get |
27 | POST | add |
28 | PUT | set |
29 | DELETE | delete |
30 |
31 |
32 | And, eventually, to pick the value (for mutation operations) it uses
33 | body of the HTTP request.
34 |
35 | Information, about how to override aforementioned behaviour using
36 | variables, can be found below: [set $couchbase_cmd][4], [set
37 | $couchbase_key][5], [set $couchbase_val][6].
38 |
39 | ## Configuration Directives
40 |
41 |
42 |
43 |
44 | syntax: |
45 | couchbase_pass address bucket=value username=value password=value; |
46 |
47 |
48 | default: |
49 | couchbase_pass localhost:8091 bucket=default; |
50 |
51 |
52 | severity: |
53 | mandatory |
54 |
55 |
56 | context: |
57 | location , if in location , limit_except |
58 |
59 |
60 |
61 | Specifies connection parameters for the cluster. Parameter `address` represents comma-separated pairs `host:port`, where `:port` might be omitted in which case default port (8091) will be used:
62 |
63 | location /cache/ {
64 | couchbase_pass example.com:8091,example.org bucket=app;
65 | }
66 |
67 | * * *
68 |
69 |
70 |
71 |
72 | syntax: |
73 | couchbase_connect_timeout time; |
74 |
75 |
76 | default: |
77 | couchbase_connect_timeout 2.5s; |
78 |
79 |
80 | severity: |
81 | optional |
82 |
83 |
84 | context: |
85 | location |
86 |
87 |
88 |
89 | Sets timeout for establishing connection to Couchbase Server.
90 |
91 | * * *
92 |
93 |
94 |
95 |
96 | syntax: |
97 | couchbase_timeout time; |
98 |
99 |
100 | default: |
101 | couchbase_timeout 2.5s; |
102 |
103 |
104 | severity: |
105 | optional |
106 |
107 |
108 | context: |
109 | location |
110 |
111 |
112 |
113 | Sets timeout for data communication with Couchbase Server.
114 |
115 | ## Variables
116 |
117 |
118 |
119 |
120 | syntax: |
121 | set $couchbase_cmd command; |
122 |
123 |
124 | default: |
125 | - |
126 |
127 |
128 | severity: |
129 | optional |
130 |
131 |
132 |
133 | Sets command type, executed for this location. For example, command
134 | can be taken from query string (`/cache/?cmd=append`):
135 |
136 | location /cache/ {
137 | set $couchbase_cmd $arg_cmd;
138 | couchbase_pass;
139 | }
140 |
141 | Or command can be fixed string:
142 |
143 | location /cache/ {
144 | set $couchbase_cmd 'append';
145 | couchbase_pass;
146 | }
147 |
148 | As for version 0.3.1 supported the following commands: `get`, `set`,
149 | `add`, `replace`, `append`, `prepend`, `delete`. About corresponding
150 | HTTP methods to commands, read section "Description".
151 |
152 | * * *
153 |
154 |
155 |
156 |
157 | syntax: |
158 | set $couchbase_key value; |
159 |
160 |
161 | default: |
162 | - |
163 |
164 |
165 | severity: |
166 | optional |
167 |
168 |
169 |
170 | Sets key for the document in this location. For example, key can be
171 | taken from query string (`/cache/?key=foo`):
172 |
173 | location /cache/ {
174 | set $couchbase_key $arg_key;
175 | couchbase_pass;
176 | }
177 |
178 | Or key can be fixed string:
179 |
180 | location /cache/ {
181 | set $couchbase_key 'foo';
182 | couchbase_pass;
183 | }
184 |
185 | * * *
186 |
187 |
188 |
189 |
190 | syntax: |
191 | set $couchbase_val value; |
192 |
193 |
194 | default: |
195 | - |
196 |
197 |
198 | severity: |
199 | optional |
200 |
201 |
202 |
203 | Picks the value for the document in this location. For example, value
204 | might be taken from query string (`/cache/?value=foo%0a`):
205 |
206 | location /cache/ {
207 | set $couchbase_val $arg_val;
208 | couchbase_pass;
209 | }
210 |
211 | Or value can be fixed string:
212 |
213 | location /cache/ {
214 | set $couchbase_val 'foo%a';
215 | couchbase_pass;
216 | }
217 |
218 | Note, that if the value specified by `$couchbase_key`, then it will be
219 | considered URL-encoded, and will be decoded back.
220 |
221 | * * *
222 |
223 |
224 |
225 |
226 | syntax: |
227 | set $couchbase_cas value; |
228 |
229 |
230 | default: |
231 | - |
232 |
233 |
234 | severity: |
235 | optional |
236 |
237 |
238 |
239 | This variable stores CAS value of the document. This value changes on
240 | each mutation of the document. Therefore it can be used for optimistic
241 | locking.
242 |
243 | location /cache/ {
244 | set $couchbase_key $arg_key;
245 | set $couchbase_val $arg_val;
246 | set $couchbase_cmd $arg_cmd;
247 | set $couchbase_cas $arg_cas;
248 | couchbase_pass;
249 | add_header X-CAS $couchbase_cas;
250 | }
251 |
252 | With configuration above, the server will set header
253 | X-CAS
with actual CAS value. This value can be used for
254 | subsequent updates, and if someone managed to changed it before, the
255 | server will respond with code 409 Conflict
, so that the
256 | client have to update local document and try again with new CAS.
257 |
258 | ## System Requirements
259 |
260 | This module has been tested with nginx v1.3.7, v1.2.6 and v1.4.0.
261 |
262 | ## Download
263 |
264 | Last version is 0.3.1: [nginx-couchbase-module-0.3.1.tar.gz][7].
265 |
266 | Project repository: [https://github.com/couchbaselabs/couchbase-nginx-module][8]
267 |
268 | ## Usage
269 |
270 | Create build directory, download and unpack all dependencies there:
271 |
272 | mkdir nginx-couchbase
273 | cd nginx-couchbase
274 | wget http://nginx.org/download/nginx-1.3.7.tar.gz
275 | wget http://packages.couchbase.com/clients/c/libcouchbase-2.0.3nginx3.tar.gz
276 | wget http://packages.couchbase.com/clients/c/nginx-couchbase-module-0.3.1.tar.gz
277 | for i in *.tar.gz; do tar xvf $i; done
278 |
279 | The following steps describe how to install nginx with module into the
280 | directory `/opt/nginx-couchbase`:
281 |
282 | export PREFIX=/opt/nginx-couchbase
283 |
284 | cd libcouchbase-2.0.3nginx3
285 | ./configure --prefix=$PREFIX --enable-debug --disable-plugins --disable-tests --disable-couchbasemock
286 | make && sudo make install
287 | cd ..
288 |
289 | cd nginx-1.3.7
290 | export LIBCOUCHBASE_INCLUDE=$PREFIX/include
291 | export LIBCOUCHBASE_LIB=$PREFIX/lib
292 | ./configure --prefix=$PREFIX --with-debug --add-module=../nginx-couchbase-module-0.3.1
293 | make && sudo make install
294 |
295 |
296 | [1]: http://couchbase.com/download
297 | [2]: http://www.nginx.ru/
298 | [3]: #couchbase_pass
299 | [4]: #set-couchbase_cmd
300 | [5]: #set-couchbase_key
301 | [6]: #set-couchbase_val
302 | [7]: http://packages.couchbase.com/clients/c/nginx-couchbase-module-0.3.1.tar.gz
303 | [8]: https://github.com/couchbaselabs/couchbase-nginx-module
304 |
--------------------------------------------------------------------------------
/doc/couchbase.ru.markdown:
--------------------------------------------------------------------------------
1 | # Nginx Couchbase Module (0.3.1)
2 |
3 | Клиент к [Couchbase][1] для [nginx][2].
4 |
5 | ## Описание
6 |
7 | Модуль транслирует запросы к кластеру Couchbase, заданному аргументом
8 | директивы [couchbase_pass][3], и возвращает результат обратно клиенту.
9 |
10 | По умолчанию, модуль может использовать информацию, доступную в
11 | запросе, чтобы сформировать пакет данных. В качестве ключа,
12 | используется часть URI, оставшаяся после отсечения совпавшей части
13 | location.
14 |
15 | location /cache/ {
16 | couchbase_pass;
17 | }
18 |
19 | В примере выше, если у сервера будет запрошен адрес
20 | `/cache/example.html`, то ключом будет `example.html`.
21 |
22 | Чтобы выбрать команду, оправляемую на Couchbase Server, используется
23 | HTTP method. Таблица ниже показывает соответствие команд методам.
24 |
25 |
26 | HTTP | Couchbase |
27 | GET, HEAD | get |
28 | POST | add |
29 | PUT | set |
30 | DELETE | delete |
31 |
32 |
33 | И, наконец, для того, чтобы выбрать значение (для операций, изменяющих
34 | его) ипользуется тело HTTP запроса.
35 |
36 | Информацию о том, как можно переопределить вышеприведённое поведение с
37 | помощью переменных, можно найти ниже: [set $couchbase_cmd][4], [set
38 | $couchbase_key][5], [set $couchbase_val][6].
39 |
40 | ## Директивы конфигурации
41 |
42 |
43 |
44 |
45 | синтаксис: |
46 | couchbase_pass address bucket=value username=value password=value; |
47 |
48 |
49 | значение по умолчанию: |
50 | couchbase_pass localhost:8091 bucket=default; |
51 |
52 |
53 | строгость: |
54 | обязательная |
55 |
56 |
57 | контекст: |
58 | location , if in location , limit_except |
59 |
60 |
61 |
62 | Задаёт параметры для подключения к кластеру. Параметр `address`
63 | представляет собой пары `host:port`, разделённые запятой, причём
64 | `:port` может быть опущен, тогда будет использован порт по умолчанию:
65 | 8091.
66 |
67 | location /cache/ {
68 | couchbase_pass example.com:8091,example.org bucket=app;
69 | }
70 |
71 | * * *
72 |
73 |
74 |
75 |
76 | синтаксис: |
77 | couchbase_connect_timeout время; |
78 |
79 |
80 | значение по умолчанию: |
81 | couchbase_connect_timeout 2.5s; |
82 |
83 |
84 | строгость: |
85 | необязательная |
86 |
87 |
88 | контекст: |
89 | location |
90 |
91 |
92 |
93 | Задаёт таймаут для установки подключения к Couchbase Server.
94 |
95 | * * *
96 |
97 |
98 |
99 |
100 | синтаксис: |
101 | couchbase_timeout время; |
102 |
103 |
104 | значение по умолчанию: |
105 | couchbase_timeout 2.5s; |
106 |
107 |
108 | строгость: |
109 | необязательная |
110 |
111 |
112 | контекст: |
113 | location |
114 |
115 |
116 |
117 | Задаёт таймаут для операций обмена данными с Couchbase Server.
118 |
119 | ## Переменные
120 |
121 |
122 |
123 |
124 | синтаксис: |
125 | set $couchbase_cmd команда; |
126 |
127 |
128 | значение по умолчанию: |
129 | - |
130 |
131 |
132 | строгость: |
133 | необязательная |
134 |
135 |
136 |
137 | Устанавливает тип команды, выполняемой для данного location. Например,
138 | команда может быть получена из query string (`/cache/?cmd=append`):
139 |
140 | location /cache/ {
141 | set $couchbase_cmd $arg_cmd;
142 | couchbase_pass;
143 | }
144 |
145 | Или можно зафиксировать команду в виде строки:
146 |
147 | location /cache/ {
148 | set $couchbase_cmd 'append';
149 | couchbase_pass;
150 | }
151 |
152 | Для версии 0.3.1 поддерживается следующий набор команд: `get`, `set`,
153 | `add`, `replace`, `append`, `prepend`, `delete`. Про соответствие HTTP
154 | методов командам можно прочитать выше в разделе "Описание".
155 |
156 | * * *
157 |
158 |
159 |
160 |
161 | синтаксис: |
162 | set $couchbase_key значение; |
163 |
164 |
165 | значение по умолчанию: |
166 | - |
167 |
168 |
169 | строгость: |
170 | необязательная |
171 |
172 |
173 |
174 | Управляет выбором ключа для документа. Например, ключ может быть
175 | получен из query string (`/cache/?key=foo`):
176 |
177 | location /cache/ {
178 | set $couchbase_key $arg_key;
179 | couchbase_pass;
180 | }
181 |
182 | Или можно зафиксировать ключ в виде строки:
183 |
184 | location /cache/ {
185 | set $couchbase_key 'foo';
186 | couchbase_pass;
187 | }
188 |
189 | * * *
190 |
191 |
192 |
193 |
194 | синтаксис: |
195 | set $couchbase_val значение; |
196 |
197 |
198 | значение по умолчанию: |
199 | - |
200 |
201 |
202 | строгость: |
203 | необязательная |
204 |
205 |
206 |
207 | Управляет выбором значения для документа. Например, значение может быть
208 | получено из query string (`/cache/?value=foo%0a`):
209 |
210 | location /cache/ {
211 | set $couchbase_val $arg_val;
212 | couchbase_pass;
213 | }
214 |
215 | Или можно зафиксировать значение в виде строки:
216 |
217 | location /cache/ {
218 | set $couchbase_val 'foo%a';
219 | couchbase_pass;
220 | }
221 |
222 | Обратите внимание, что если значение приходит из переменной
223 | `$couchbase_key`, оно будет переведено из URL-кодировки.
224 |
225 | * * *
226 |
227 |
228 |
229 |
230 | синтаксис: |
231 | set $couchbase_cas значение; |
232 |
233 |
234 | значение по умолчанию: |
235 | - |
236 |
237 |
238 | строгость: |
239 | необязательная |
240 |
241 |
242 |
243 | Переменная хранит CAS значение документа. Это значение меняется при
244 | каждом изменении документа. Таким образом его можно использовать для
245 | оптимистический блокировки.
246 |
247 | location /cache/ {
248 | set $couchbase_key $arg_key;
249 | set $couchbase_val $arg_val;
250 | set $couchbase_cmd $arg_cmd;
251 | set $couchbase_cas $arg_cas;
252 | couchbase_pass;
253 | add_header X-CAS $couchbase_cas;
254 | }
255 |
256 | С такой конфигурацией для каждой операции будет установлен заголовок
257 | X-CAS
со актуальным значением CAS. Это значение может
258 | быть использовано при последующем обновлении, и в случае, если кто-то
259 | успел изменить его, будет выдан код 409 Conflict
, так что
260 | клиент должен будет обновить локальный документ и попробовать ещё с
261 | новым CAS.
262 |
263 | ## Системные требования
264 |
265 | Данный модуль тестировался на совместимость с версией nginx v1.3.7,
266 | v1.2.6 и v1.4.0.
267 |
268 | ## Скачать
269 |
270 | Последняя версия 0.3.1: [nginx-couchbase-module-0.3.1.tar.gz][7].
271 |
272 | Репозиторий проекта: [https://github.com/couchbaselabs/couchbase-nginx-module][8]
273 |
274 | ## Как использовать
275 |
276 | Создать директорию для сборки, скачать и распаковать туда все
277 | зависимости:
278 |
279 | mkdir nginx-couchbase
280 | cd nginx-couchbase
281 | wget http://nginx.org/download/nginx-1.3.7.tar.gz
282 | wget http://packages.couchbase.com/clients/c/libcouchbase-2.0.3nginx3.tar.gz
283 | wget http://packages.couchbase.com/clients/c/nginx-couchbase-module-0.3.1.tar.gz
284 | for i in *.tar.gz; do tar xvf $i; done
285 |
286 | Последовательность шагов ниже установит nginx с модулем в директорию
287 | `/opt/nginx-couchbase`:
288 |
289 | export PREFIX=/opt/nginx-couchbase
290 |
291 | cd libcouchbase-2.0.3nginx3
292 | ./configure --prefix=$PREFIX --enable-debug --disable-plugins --disable-tests --disable-couchbasemock
293 | make && sudo make install
294 | cd ..
295 |
296 | cd nginx-1.3.7
297 | export LIBCOUCHBASE_INCLUDE=$PREFIX/include
298 | export LIBCOUCHBASE_LIB=$PREFIX/lib
299 | ./configure --prefix=$PREFIX --with-debug --add-module=../nginx-couchbase-module-0.3.1
300 | make && sudo make install
301 |
302 |
303 | [1]: http://couchbase.com/download
304 | [2]: http://www.nginx.ru/
305 | [3]: #couchbase_pass
306 | [4]: #set-couchbase_cmd
307 | [5]: #set-couchbase_key
308 | [6]: #set-couchbase_val
309 | [7]: http://packages.couchbase.com/clients/c/nginx-couchbase-module-0.3.1.tar.gz
310 | [8]: https://github.com/couchbaselabs/couchbase-nginx-module
311 |
--------------------------------------------------------------------------------
/etc/nginx.conf:
--------------------------------------------------------------------------------
1 | # vim:ft=nginx:
2 |
3 | worker_processes 1;
4 | # daemon off;
5 | # debug_points abort;
6 |
7 | error_log logs/error.log debug;
8 |
9 | events {
10 | worker_connections 1024;
11 | }
12 |
13 | http {
14 | upstream ruby {
15 | server localhost:9292;
16 | }
17 |
18 | server {
19 | listen 8080;
20 | server_name localhost;
21 |
22 | location /lcb {
23 | set $couchbase_key $arg_key;
24 | set $couchbase_cmd $arg_cmd;
25 | set $couchbase_val $arg_val;
26 | set $couchbase_flags $arg_flags;
27 | set $couchbase_exptime $arg_exptime;
28 | couchbase_connect_timeout 6ms;
29 | couchbase_timeout 3ms;
30 | couchbase_pass localhost:8091,127.0.0.1:8091,localhost bucket=default;
31 | add_header X-Couchbase-CAS $couchbase_cas;
32 | add_header X-Couchbase-Flags $couchbase_flags;
33 | }
34 |
35 | location /cb {
36 | internal;
37 | set $couchbase_key $arg_key;
38 | set $couchbase_cmd $arg_cmd;
39 | set $couchbase_val $arg_val;
40 | couchbase_connect_timeout 6ms;
41 | couchbase_timeout 3ms;
42 | couchbase_pass localhost:8091,127.0.0.1:8091,localhost bucket=default;
43 | }
44 |
45 | location /e {
46 | set $key $uri$args;
47 | srcache_fetch GET /cb key=$key;
48 | srcache_store PUT /cb key=$key;
49 | srcache_store_statuses 200 301 302;
50 | echo "Hello, World!\n";
51 | }
52 |
53 | location /c {
54 | set $key $uri$args;
55 | srcache_fetch GET /cb key=$key;
56 | srcache_store PUT /cb key=$key;
57 | srcache_store_statuses 200 301 302;
58 | proxy_pass http://ruby;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/etc/nginx.stress.conf:
--------------------------------------------------------------------------------
1 | # vim:ft=nginx:
2 |
3 | worker_processes 2;
4 |
5 | error_log logs/error.log error;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | server {
13 | listen 8080;
14 | server_name localhost;
15 |
16 | location /lcb {
17 | set $couchbase_key $arg_key;
18 | set $couchbase_cmd $arg_cmd;
19 | set $couchbase_val $arg_val;
20 | set $couchbase_flags $arg_flags;
21 | set $couchbase_exptime $arg_exptime;
22 | couchbase_connect_timeout 6ms;
23 | couchbase_timeout 3ms;
24 | couchbase_pass localhost:8091,127.0.0.1:8091,localhost bucket=default;
25 | add_header X-Couchbase-CAS $couchbase_cas;
26 | add_header X-Couchbase-Flags $couchbase_flags;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/ddebug.h:
--------------------------------------------------------------------------------
1 | #ifndef DDEBUG_H
2 | #define DDEBUG_H
3 |
4 | #include
5 |
6 | #if defined(DDEBUG) && (DDEBUG)
7 |
8 | # if (NGX_HAVE_VARIADIC_MACROS)
9 |
10 | # define dd(...) \
11 | fprintf(stderr, "couchbase *** %s:%d ", __func__, __LINE__); \
12 | fprintf(stderr, __VA_ARGS__); \
13 | fprintf(stderr, "\n")
14 |
15 | # else
16 |
17 | #include
18 | #include
19 |
20 | static void dd(const char *fmt, ...)
21 | {
22 | }
23 |
24 | # endif
25 |
26 | #else
27 |
28 | # if (NGX_HAVE_VARIADIC_MACROS)
29 |
30 | # define dd(...)
31 |
32 | # else
33 |
34 | #include
35 |
36 | static void dd(const char *fmt, ...)
37 | {
38 | }
39 |
40 | # endif
41 |
42 | #endif
43 |
44 | #endif /* DDEBUG_H */
45 |
--------------------------------------------------------------------------------
/src/ngx_lcb_callbacks.c:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 | /*
3 | * Copyright 2013 Couchbase, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | #ifndef DDEBUG
19 | #define DDEBUG 0
20 | #endif
21 | #include "ddebug.h"
22 |
23 | #include "ngx_lcb_module.h"
24 |
25 | #ifndef NGX_HTTP_UNPROCESSABLE_ENTITY
26 | #define NGX_HTTP_UNPROCESSABLE_ENTITY 422
27 | #endif
28 |
29 | typedef struct ngx_lcb_error_s {
30 | lcb_error_t rc;
31 | ngx_str_t errmsg;
32 | ngx_int_t status;
33 | } ngx_lcb_error_t;
34 |
35 | static ngx_lcb_error_t ngx_lcb_errors[] = {
36 | {LCB_SUCCESS, ngx_string("success"), NGX_HTTP_OK},
37 | {LCB_AUTH_CONTINUE, ngx_string("auth_continue"), NGX_HTTP_INTERNAL_SERVER_ERROR},
38 | {LCB_AUTH_ERROR, ngx_string("auth_error"), NGX_HTTP_INTERNAL_SERVER_ERROR},
39 | {LCB_DELTA_BADVAL, ngx_string("delta_badval"), NGX_HTTP_UNPROCESSABLE_ENTITY},
40 | {LCB_E2BIG, ngx_string("e2big"), NGX_HTTP_INTERNAL_SERVER_ERROR},
41 | {LCB_EBUSY, ngx_string("ebusy"), NGX_HTTP_INTERNAL_SERVER_ERROR},
42 | {LCB_EINTERNAL, ngx_string("einternal"), NGX_HTTP_INTERNAL_SERVER_ERROR},
43 | {LCB_EINVAL, ngx_string("einval"), NGX_HTTP_INTERNAL_SERVER_ERROR},
44 | {LCB_ENOMEM, ngx_string("enomem"), NGX_HTTP_INTERNAL_SERVER_ERROR},
45 | {LCB_ERANGE, ngx_string("erange"), NGX_HTTP_INTERNAL_SERVER_ERROR},
46 | {LCB_ERROR, ngx_string("error"), NGX_HTTP_INTERNAL_SERVER_ERROR},
47 | {LCB_ETMPFAIL, ngx_string("etmp_fail"), NGX_HTTP_INTERNAL_SERVER_ERROR},
48 | {LCB_KEY_EEXISTS, ngx_string("key_eexists"), NGX_HTTP_CONFLICT},
49 | {LCB_KEY_ENOENT, ngx_string("key_enoent"), NGX_HTTP_NOT_FOUND},
50 | {LCB_DLOPEN_FAILED, ngx_string("dlopen_failed"), NGX_HTTP_INTERNAL_SERVER_ERROR},
51 | {LCB_DLSYM_FAILED, ngx_string("dlsym_failed"), NGX_HTTP_INTERNAL_SERVER_ERROR},
52 | {LCB_NETWORK_ERROR, ngx_string("network_error"), NGX_HTTP_INTERNAL_SERVER_ERROR},
53 | {LCB_NOT_MY_VBUCKET, ngx_string("not_my_vbucket"), NGX_HTTP_INTERNAL_SERVER_ERROR},
54 | {LCB_NOT_STORED, ngx_string("not_stored"), NGX_HTTP_UNPROCESSABLE_ENTITY},
55 | {LCB_NOT_SUPPORTED, ngx_string("not_supported"), NGX_HTTP_INTERNAL_SERVER_ERROR},
56 | {LCB_UNKNOWN_COMMAND, ngx_string("unknown_command"), NGX_HTTP_INTERNAL_SERVER_ERROR},
57 | {LCB_UNKNOWN_HOST, ngx_string("unknown_host"), NGX_HTTP_INTERNAL_SERVER_ERROR},
58 | {LCB_PROTOCOL_ERROR, ngx_string("protocol_error"), NGX_HTTP_INTERNAL_SERVER_ERROR},
59 | {LCB_ETIMEDOUT, ngx_string("etimeout"), NGX_HTTP_REQUEST_TIME_OUT},
60 | {LCB_CONNECT_ERROR, ngx_string("connect_error"), NGX_HTTP_INTERNAL_SERVER_ERROR},
61 | {LCB_BUCKET_ENOENT, ngx_string("bucket_enoent"), NGX_HTTP_NOT_FOUND},
62 | {LCB_CLIENT_ENOMEM, ngx_string("client_enomem"), NGX_HTTP_INTERNAL_SERVER_ERROR},
63 | {LCB_CLIENT_ETMPFAIL, ngx_string("client_etmpfail"), NGX_HTTP_INTERNAL_SERVER_ERROR},
64 | {LCB_EBADHANDLE, ngx_string("ebadhandle"), NGX_HTTP_INTERNAL_SERVER_ERROR},
65 | {LCB_SERVER_BUG, ngx_string("server_bug"), NGX_HTTP_INTERNAL_SERVER_ERROR},
66 | {LCB_PLUGIN_VERSION_MISMATCH, ngx_string("plugin_version_mismatch"), NGX_HTTP_INTERNAL_SERVER_ERROR},
67 |
68 | {0, ngx_null_string, NGX_HTTP_INTERNAL_SERVER_ERROR}
69 | };
70 |
71 |
72 | static void
73 | ngx_lcb_finalize_request(ngx_http_request_t *r, lcb_error_t rc)
74 | {
75 | if (rc == 0
76 | && !r->header_only
77 | #if (NGX_HTTP_CACHE)
78 | && !r->cached
79 | #endif
80 | ) {
81 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "will send special NGX_HTTP_LAST %s:%d", __FILE__, __LINE__);
82 | rc = ngx_http_send_special(r, NGX_HTTP_LAST);
83 | }
84 | ngx_http_finalize_request(r, rc);
85 | if (r->connection->data) {
86 | ngx_http_run_posted_requests(r->connection);
87 | }
88 | }
89 |
90 | static ngx_err_t
91 | cb_format_lcb_error(lcb_t instance, ngx_http_request_t *r, lcb_error_t rc, ngx_str_t *str)
92 | {
93 | ngx_lcb_error_t *e;
94 | const u_char *ptr;
95 | ngx_str_t error = ngx_string("unknown_error");
96 | const u_char *reason = (const u_char *)"Unknown error code";
97 |
98 |
99 | e = ngx_lcb_errors;
100 | r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR;
101 | while (e->errmsg.data != NULL) {
102 | if (rc == e->rc) {
103 | error = e->errmsg;
104 | reason = (const u_char *)lcb_strerror(NULL, rc);
105 | r->headers_out.status = e->status;
106 | break;
107 | }
108 | e++;
109 | }
110 |
111 | str->len = error.len + ngx_strlen(reason) + 24;
112 | str->data = ngx_pnalloc(r->pool, str->len);
113 | if (str->data == NULL) {
114 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
115 | "couchbase(%p): failed to allocate buffer while formatting libcouchbase error", instance);
116 | return NGX_ERROR;
117 | }
118 | ptr = ngx_sprintf(str->data, "{\"error\":\"%V\",\"reason\":\"%s\"}", &error, reason);
119 | if ((size_t)(ptr - str->data) != str->len) {
120 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
121 | "couchbase(%p): failed to format libcouchbase error", instance);
122 | return NGX_ERROR;
123 | }
124 | return NGX_OK;
125 | }
126 |
127 | void
128 | ngx_lcb_configuration_callback(lcb_t instance, lcb_configuration_t config)
129 | {
130 | if (config == LCB_CONFIGURATION_NEW) {
131 | ngx_lcb_connection_t *conn;
132 | ngx_lcb_loc_conf_t *cblcf;
133 | ngx_http_request_t **r;
134 | ngx_uint_t ii;
135 |
136 | conn = (ngx_lcb_connection_t *)lcb_get_cookie(instance);
137 | conn->connected = 1;
138 | if (conn->backlog.nelts < 1) {
139 | ngx_log_error(NGX_LOG_WARN, conn->log, 0,
140 | "couchbase(%p): configuration callback without delayed responses",
141 | (void *)instance);
142 | return;
143 | }
144 | r = conn->backlog.elts;
145 | cblcf = ngx_http_get_module_loc_conf(r[0], ngx_http_couchbase_module);
146 | (void)lcb_set_timeout(instance, cblcf->timeout * 1000); /* in usec */
147 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r[0]->connection->log, 0,
148 | "couchbase(%p): the instance has been connected. timeout:%Mms",
149 | (void *)instance, cblcf->timeout);
150 |
151 | for (ii = 0; ii < conn->backlog.nelts; ++ii) {
152 | ngx_lcb_process(r[ii]);
153 | }
154 | conn->backlog.nelts = 0;
155 | }
156 | }
157 |
158 | ngx_err_t
159 | ngx_lcb_request_set_cas(lcb_t instance, ngx_http_request_t *r, lcb_cas_t cas)
160 | {
161 | ngx_http_variable_value_t *cas_vv;
162 |
163 | cas_vv = ngx_http_get_indexed_variable(r, ngx_lcb_cas_idx);
164 | if (cas_vv == NULL) {
165 | return NGX_ERROR;
166 | }
167 | if (cas_vv->not_found) {
168 | cas_vv->not_found = 0;
169 | cas_vv->valid = 1;
170 | cas_vv->no_cacheable = 0;
171 | }
172 | cas_vv->data = ngx_pnalloc(r->pool, NGX_UINT64_T_LEN);
173 | if (cas_vv->data == NULL) {
174 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
175 | "couchbase(%p): failed to allocate buffer for $couchbase_cas variable", instance);
176 | return NGX_ERROR;
177 | }
178 | cas_vv->len = ngx_sprintf(cas_vv->data, "%uL", (uint64_t)cas) - cas_vv->data;
179 | return NGX_OK;
180 | }
181 |
182 | ngx_err_t
183 | ngx_lcb_request_set_flags(lcb_t instance, ngx_http_request_t *r, lcb_uint32_t flags)
184 | {
185 | ngx_http_variable_value_t *flags_vv;
186 |
187 | flags_vv = ngx_http_get_indexed_variable(r, ngx_lcb_flags_idx);
188 | if (flags_vv == NULL) {
189 | return NGX_ERROR;
190 | }
191 | if (flags_vv->not_found) {
192 | flags_vv->not_found = 0;
193 | flags_vv->valid = 1;
194 | flags_vv->no_cacheable = 0;
195 | }
196 | flags_vv->data = ngx_pnalloc(r->pool, NGX_UINT32_T_LEN);
197 | if (flags_vv->data == NULL) {
198 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
199 | "couchbase(%p): failed to allocate buffer for $couchbase_flags variable", instance);
200 | return NGX_ERROR;
201 | }
202 | flags_vv->len = ngx_sprintf(flags_vv->data, "%ui", (ngx_uint_t)flags) - flags_vv->data;
203 | return NGX_OK;
204 | }
205 |
206 | void
207 | ngx_lcb_store_callback(lcb_t instance, const void *cookie,
208 | lcb_storage_t operation, lcb_error_t error,
209 | const lcb_store_resp_t *item)
210 | {
211 | ngx_http_request_t *r = (ngx_http_request_t *)cookie;
212 | ngx_chain_t out;
213 | ngx_buf_t *b;
214 | ngx_int_t rc;
215 |
216 | ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
217 | "couchbase(%p): store response \"%*s\", status: 0x%02xd, operation: 0x%02xd",
218 | (void *)instance, item->v.v0.nkey, item->v.v0.key, error, operation);
219 |
220 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
221 | if (b == NULL) {
222 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
223 | return;
224 | }
225 | out.buf = b;
226 | out.next = NULL;
227 | b->memory = 1;
228 | b->last_buf = 1;
229 |
230 | if (error == LCB_SUCCESS) {
231 | if (ngx_lcb_request_set_cas(instance, r, item->v.v0.cas) != NGX_OK) {
232 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
233 | return;
234 | }
235 | r->headers_out.status = NGX_HTTP_CREATED;
236 | r->headers_out.content_length_n = 0;
237 | r->header_only = 1;
238 | } else {
239 | ngx_str_t errstr;
240 |
241 | rc = cb_format_lcb_error(instance, r, error, &errstr);
242 | if (rc != NGX_OK) {
243 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
244 | return;
245 | }
246 | b->pos = errstr.data;
247 | b->last = errstr.data + errstr.len;
248 | r->headers_out.content_length_n = errstr.len;
249 | }
250 |
251 | rc = ngx_http_send_header(r);
252 | if (rc == NGX_ERROR || rc > NGX_OK) {
253 | ngx_http_finalize_request(r, rc);
254 | return;
255 | }
256 | if (!r->header_only) {
257 | rc = ngx_http_output_filter(r, &out);
258 | }
259 | ngx_lcb_finalize_request(r, rc);
260 | }
261 |
262 | void
263 | ngx_lcb_remove_callback(lcb_t instance, const void *cookie,
264 | lcb_error_t error, const lcb_remove_resp_t *item)
265 | {
266 | ngx_http_request_t *r = (ngx_http_request_t *)cookie;
267 | ngx_chain_t out;
268 | ngx_buf_t *b;
269 | ngx_int_t rc;
270 |
271 | ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
272 | "couchbase(%p): remove response \"%*s\", status: 0x%02xd",
273 | (void *)instance, item->v.v0.nkey, item->v.v0.key, error);
274 |
275 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
276 | if (b == NULL) {
277 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
278 | return;
279 | }
280 | out.buf = b;
281 | out.next = NULL;
282 | b->memory = 1;
283 | b->last_buf = 1;
284 |
285 | if (error == LCB_SUCCESS) {
286 | if (ngx_lcb_request_set_cas(instance, r, (uint64_t)item->v.v0.cas) != NGX_OK) {
287 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
288 | return;
289 | }
290 | r->headers_out.status = NGX_HTTP_OK;
291 | r->headers_out.content_length_n = 0;
292 | r->header_only = 1;
293 | } else {
294 | ngx_str_t errstr;
295 |
296 | rc = cb_format_lcb_error(instance, r, error, &errstr);
297 | if (rc != NGX_OK) {
298 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
299 | return;
300 | }
301 | b->pos = errstr.data;
302 | b->last = errstr.data + errstr.len;
303 | r->headers_out.content_length_n = errstr.len;
304 | }
305 |
306 | rc = ngx_http_send_header(r);
307 | if (rc == NGX_ERROR || rc > NGX_OK) {
308 | ngx_http_finalize_request(r, rc);
309 | return;
310 | }
311 | if (!r->header_only) {
312 | rc = ngx_http_output_filter(r, &out);
313 | }
314 | ngx_lcb_finalize_request(r, rc);
315 | }
316 |
317 | void
318 | ngx_lcb_get_callback(lcb_t instance, const void *cookie, lcb_error_t error,
319 | const lcb_get_resp_t *item)
320 | {
321 | ngx_http_request_t *r = (ngx_http_request_t *)cookie;
322 | ngx_chain_t out;
323 | ngx_buf_t *b;
324 | ngx_int_t rc;
325 |
326 | ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
327 | "couchbase(%p): get response \"%*s\", status: 0x%02xd",
328 | (void *)instance, item->v.v0.nkey, item->v.v0.key, error);
329 |
330 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
331 | if (b == NULL) {
332 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
333 | return;
334 | }
335 | out.buf = b;
336 | out.next = NULL;
337 | b->memory = 1;
338 | b->last_buf = 1;
339 |
340 | if (error == LCB_SUCCESS) {
341 | if (ngx_lcb_request_set_cas(instance, r, (uint64_t)item->v.v0.cas) != NGX_OK) {
342 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
343 | return;
344 | }
345 | if (ngx_lcb_request_set_flags(instance, r, item->v.v0.flags) != NGX_OK) {
346 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
347 | return;
348 | }
349 | b->pos = (u_char *)item->v.v0.bytes;
350 | b->last = (u_char *)item->v.v0.bytes + item->v.v0.nbytes;
351 | r->headers_out.content_length_n = item->v.v0.nbytes;
352 | r->headers_out.status = NGX_HTTP_OK;
353 | } else {
354 | ngx_str_t errstr;
355 |
356 | rc = cb_format_lcb_error(instance, r, error, &errstr);
357 | if (rc != NGX_OK) {
358 | ngx_log_error(NGX_LOG_ERR, r->connection->log, rc,
359 | "couchbase: failed to format libcouchbase error 0x%02xd", rc);
360 | return;
361 | }
362 | b->pos = errstr.data;
363 | b->last = errstr.data + errstr.len;
364 | r->headers_out.content_length_n = errstr.len;
365 | }
366 |
367 | rc = ngx_http_send_header(r);
368 | if (rc == NGX_ERROR || rc > NGX_OK) {
369 | ngx_http_finalize_request(r, rc);
370 | return;
371 | }
372 | if (!r->header_only) {
373 | rc = ngx_http_output_filter(r, &out);
374 | } else {
375 | rc = NGX_DONE;
376 | }
377 | ngx_lcb_finalize_request(r, rc);
378 | }
379 |
--------------------------------------------------------------------------------
/src/ngx_lcb_callbacks.h:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 | /*
3 | * Copyright 2013 Couchbase, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | #ifndef NGX_HTTP_COUCHBASE_CALLBACKS_H
19 | #define NGX_HTTP_COUCHBASE_CALLBACKS_H
20 |
21 | void ngx_lcb_get_callback(lcb_t instance, const void *cookie, lcb_error_t error, const lcb_get_resp_t *item);
22 | void ngx_lcb_remove_callback(lcb_t instance, const void *cookie, lcb_error_t error, const lcb_remove_resp_t *item);
23 | void ngx_lcb_store_callback(lcb_t instance, const void *cookie, lcb_storage_t operation, lcb_error_t error, const lcb_store_resp_t *item);
24 | void ngx_lcb_configuration_callback(lcb_t instance, lcb_configuration_t config);
25 |
26 | #endif /* NGX_HTTP_COUCHBASE_CALLBACKS_H */
27 |
28 |
--------------------------------------------------------------------------------
/src/ngx_lcb_module.c:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 | /*
3 | * Copyright 2013 Couchbase, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | #ifndef DDEBUG
19 | #define DDEBUG 0
20 | #endif
21 | #include "ddebug.h"
22 |
23 | #include "ngx_lcb_module.h"
24 |
25 | static ngx_int_t ngx_lcb_init_process(ngx_cycle_t *cycle);
26 | static void ngx_lcb_exit_process(ngx_cycle_t *cycle);
27 | static void *ngx_lcb_create_main_conf(ngx_conf_t *cf);
28 | static void *ngx_lcb_create_loc_conf(ngx_conf_t *cf);
29 | static char *ngx_lcb_merge_loc_conf(ngx_conf_t *cf, void *prev, void *conf);
30 | static char *ngx_lcb_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
31 | static ngx_int_t ngx_lcb_postconf(ngx_conf_t *cf);
32 |
33 | static ngx_flag_t ngx_lcb_enabled = 0;
34 |
35 | static ngx_str_t ngx_lcb_cmd = ngx_string("couchbase_cmd");
36 | static ngx_str_t ngx_lcb_key = ngx_string("couchbase_key");
37 | static ngx_str_t ngx_lcb_val = ngx_string("couchbase_val");
38 | static ngx_str_t ngx_lcb_cas = ngx_string("couchbase_cas");
39 | static ngx_str_t ngx_lcb_flags = ngx_string("couchbase_flags");
40 | static ngx_str_t ngx_lcb_exptime = ngx_string("couchbase_exptime");
41 | ngx_int_t ngx_lcb_cmd_idx;
42 | ngx_int_t ngx_lcb_key_idx;
43 | ngx_int_t ngx_lcb_val_idx;
44 | ngx_int_t ngx_lcb_cas_idx;
45 | ngx_int_t ngx_lcb_flags_idx;
46 | ngx_int_t ngx_lcb_exptime_idx;
47 |
48 | enum ngx_lcb_cmd {
49 | ngx_lcb_cmd_add = 0x01, /* LCB_ADD */
50 | ngx_lcb_cmd_replace = 0x02, /* LCB_REPLACE */
51 | ngx_lcb_cmd_set = 0x03, /* LCB_SET */
52 | ngx_lcb_cmd_append = 0x04, /* LCB_APPEND */
53 | ngx_lcb_cmd_prepend = 0x05, /* LCB_PREPEND */
54 | ngx_lcb_cmd_get,
55 | ngx_lcb_cmd_delete
56 | };
57 |
58 | static struct ngx_lcb_cookie_s lcb_cookie;
59 | static ngx_array_t lcb_connections; /* ngx_lcb_connection_t */
60 | static ngx_lcb_connection_t *ngx_http_get_couchbase_connection(ngx_str_t name);
61 |
62 | static ngx_command_t ngx_lcb_commands[] = {
63 |
64 | {
65 | ngx_string("couchbase_pass"),
66 | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_TAKE1234,
67 | ngx_lcb_pass,
68 | NGX_HTTP_LOC_CONF_OFFSET,
69 | 0,
70 | NULL
71 | },
72 |
73 | {
74 | ngx_string("couchbase_connect_timeout"),
75 | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
76 | ngx_conf_set_msec_slot,
77 | NGX_HTTP_LOC_CONF_OFFSET,
78 | offsetof(ngx_lcb_loc_conf_t, connect_timeout),
79 | NULL
80 | },
81 |
82 | {
83 | ngx_string("couchbase_timeout"),
84 | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
85 | ngx_conf_set_msec_slot,
86 | NGX_HTTP_LOC_CONF_OFFSET,
87 | offsetof(ngx_lcb_loc_conf_t, timeout),
88 | NULL
89 | },
90 |
91 | ngx_null_command
92 | };
93 |
94 | static ngx_http_module_t ngx_lcb_module_ctx = {
95 | NULL, /* preconfiguration */
96 | ngx_lcb_postconf, /* postconfiguration */
97 |
98 | ngx_lcb_create_main_conf, /* create main configuration */
99 | NULL, /* init main configuration */
100 |
101 | NULL, /* create server configuration */
102 | NULL, /* merge server configuration */
103 |
104 | ngx_lcb_create_loc_conf, /* create location configuration */
105 | ngx_lcb_merge_loc_conf /* merge location configuration */
106 | };
107 |
108 | ngx_module_t ngx_http_couchbase_module = {
109 | NGX_MODULE_V1,
110 | &ngx_lcb_module_ctx, /* module context */
111 | ngx_lcb_commands, /* module directives */
112 | NGX_HTTP_MODULE, /* module type */
113 | NULL, /* init master */
114 | NULL, /* init module */
115 | ngx_lcb_init_process, /* init process */
116 | NULL, /* init thread */
117 | NULL, /* exit thread */
118 | ngx_lcb_exit_process, /* exit process */
119 | NULL, /* exit master */
120 | NGX_MODULE_V1_PADDING
121 | };
122 |
123 | static ngx_err_t
124 | ngx_lcb_atocas(u_char *line, size_t n, lcb_cas_t *cas)
125 | {
126 | lcb_cas_t value;
127 |
128 | if (n == 0 || n > NGX_UINT64_T_LEN) {
129 | return NGX_ERROR;
130 | }
131 |
132 | for (value = 0; n--; line++) {
133 | if (*line < '0' || *line > '9') {
134 | return NGX_ERROR;
135 | }
136 |
137 | value = value * 10 + (*line - '0');
138 | }
139 |
140 | *cas = value;
141 | return NGX_OK;
142 | }
143 |
144 | ngx_int_t
145 | ngx_lcb_process(ngx_http_request_t *r)
146 | {
147 | lcb_error_t err = LCB_NOT_SUPPORTED;
148 | ngx_http_variable_value_t *vv;
149 | ngx_str_t key, val;
150 | enum ngx_lcb_cmd opcode;
151 | ngx_http_core_loc_conf_t *clcf;
152 | ngx_lcb_connection_t *conn;
153 | lcb_cas_t cas = 0;
154 | lcb_uint32_t flags = 0, exptime = 0;
155 | ngx_int_t need_free_value = 0;
156 |
157 | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
158 | conn = ngx_http_get_couchbase_connection(clcf->name);
159 | if (conn == NULL) {
160 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
161 | "couchbase: connection not found: \"%V\"", &clcf->name);
162 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
163 | }
164 |
165 | /* setup command: use variable or fallback to HTTP method */
166 | vv = ngx_http_get_indexed_variable(r, ngx_lcb_cmd_idx);
167 | if (vv == NULL) {
168 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
169 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
170 | }
171 | if (vv->not_found || vv->len == 0) {
172 | if (r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)) {
173 | opcode = ngx_lcb_cmd_get;
174 | } else if (r->method == NGX_HTTP_POST) {
175 | opcode = ngx_lcb_cmd_add;
176 | } else if (r->method == NGX_HTTP_PUT) {
177 | opcode = ngx_lcb_cmd_set;
178 | } else if (r->method == NGX_HTTP_DELETE) {
179 | opcode = ngx_lcb_cmd_delete;
180 | } else {
181 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
182 | "ngx_memc: $memc_cmd variable requires explicit "
183 | "assignment for HTTP request method %V",
184 | &r->method_name);
185 | ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
186 | return NGX_HTTP_BAD_REQUEST;
187 | }
188 | } else {
189 | if (ngx_strncmp(vv->data, "get", 3) == 0) {
190 | opcode = ngx_lcb_cmd_get;
191 | } else if (ngx_strncmp(vv->data, "add", 3) == 0) {
192 | opcode = ngx_lcb_cmd_add;
193 | } else if (ngx_strncmp(vv->data, "replace", 7) == 0) {
194 | opcode = ngx_lcb_cmd_replace;
195 | } else if (ngx_strncmp(vv->data, "set", 3) == 0) {
196 | opcode = ngx_lcb_cmd_set;
197 | } else if (ngx_strncmp(vv->data, "append", 6) == 0) {
198 | opcode = ngx_lcb_cmd_append;
199 | } else if (ngx_strncmp(vv->data, "prepend", 7) == 0) {
200 | opcode = ngx_lcb_cmd_prepend;
201 | } else if (ngx_strncmp(vv->data, "delete", 6) == 0) {
202 | opcode = ngx_lcb_cmd_delete;
203 | } else {
204 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
205 | "ngx_memc: unknown $couchbase_cmd \"%v\"", vv);
206 | ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
207 | return NGX_HTTP_BAD_REQUEST;
208 | }
209 | }
210 |
211 | /* setup key: use variable or fallback to URI */
212 | vv = ngx_http_get_indexed_variable(r, ngx_lcb_key_idx);
213 | if (vv == NULL) {
214 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
215 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
216 | }
217 | if (vv->not_found || vv->len == 0) {
218 | size_t loc_len;
219 |
220 | loc_len = r->valid_location ? clcf->name.len : 0;
221 | key.data = r->uri.data + loc_len;
222 | key.len = r->uri.len - loc_len;
223 | } else {
224 | u_char *dst, *src;
225 | key.data = ngx_palloc(r->pool, vv->len);
226 | if (key.data == NULL) {
227 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
228 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
229 | }
230 | dst = key.data;
231 | src = vv->data;
232 | ngx_unescape_uri(&dst, &src, vv->len, 0);
233 | *dst = 0;
234 | key.len = dst - key.data;
235 | }
236 |
237 | /* setup value: use variable or fallback to HTTP body */
238 | ngx_str_null(&val);
239 | if (opcode >= ngx_lcb_cmd_add && opcode <= ngx_lcb_cmd_prepend) {
240 | vv = ngx_http_get_indexed_variable(r, ngx_lcb_val_idx);
241 | if (vv == NULL) {
242 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
243 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
244 | }
245 | if (vv->not_found || vv->len == 0) {
246 | if (r->request_body == NULL || r->request_body->bufs == NULL) {
247 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
248 | "neither the \"$couchbase_value\" variable "
249 | "nor the request body is available");
250 | ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
251 | return NGX_HTTP_BAD_REQUEST;
252 | } else {
253 | /* copy body to the buffer */
254 | ngx_chain_t *cl;
255 | u_char *p;
256 |
257 | val.len = 0;
258 | for (cl = r->request_body->bufs; cl; cl = cl->next) {
259 | val.len += ngx_buf_size(cl->buf);
260 | }
261 | p = val.data = ngx_palloc(r->pool, val.len);
262 | if (p == NULL) {
263 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
264 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
265 | }
266 | for (cl = r->request_body->bufs; cl; cl = cl->next) {
267 | p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos);
268 | }
269 | vv = NULL;
270 | }
271 | } else {
272 | u_char *dst, *src;
273 | val.data = ngx_palloc(r->pool, vv->len);
274 | if (val.data == NULL) {
275 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
276 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
277 | }
278 | need_free_value = 1;
279 | dst = val.data;
280 | src = vv->data;
281 | ngx_unescape_uri(&dst, &src, vv->len, 0);
282 | *dst = 0;
283 | val.len = dst - val.data;
284 | }
285 | }
286 |
287 | /* setup CAS value */
288 | vv = ngx_http_get_indexed_variable(r, ngx_lcb_cas_idx);
289 | if (vv == NULL) {
290 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
291 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
292 | }
293 | if (!vv->not_found && vv->len > 0) {
294 | if (ngx_lcb_atocas(vv->data, vv->len, &cas) != NGX_OK) {
295 | ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
296 | return NGX_HTTP_BAD_REQUEST;
297 | }
298 | }
299 |
300 | /* setup flags value */
301 | vv = ngx_http_get_indexed_variable(r, ngx_lcb_flags_idx);
302 | if (vv == NULL) {
303 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
304 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
305 | }
306 | if (!vv->not_found && vv->len > 0) {
307 | flags = ngx_atoi(vv->data, vv->len);
308 | }
309 |
310 | /* setup expiration value */
311 | vv = ngx_http_get_indexed_variable(r, ngx_lcb_exptime_idx);
312 | if (vv == NULL) {
313 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
314 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
315 | }
316 | if (!vv->not_found && vv->len > 0) {
317 | exptime = ngx_atoi(vv->data, vv->len);
318 | }
319 |
320 | switch (opcode) {
321 | case ngx_lcb_cmd_get: {
322 | lcb_get_cmd_t cmd;
323 | const lcb_get_cmd_t *commands[1];
324 |
325 | commands[0] = &cmd;
326 | memset(&cmd, 0, sizeof(cmd));
327 | cmd.v.v0.key = key.data;
328 | cmd.v.v0.nkey = key.len;
329 | cmd.v.v0.exptime = exptime;
330 | err = lcb_get(conn->lcb, r, 1, commands);
331 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
332 | "couchbase(%p): get request \"%*s\"",
333 | (void *)conn->lcb, cmd.v.v0.nkey, cmd.v.v0.key);
334 | }
335 | break;
336 | case ngx_lcb_cmd_add:
337 | case ngx_lcb_cmd_replace:
338 | case ngx_lcb_cmd_set:
339 | case ngx_lcb_cmd_append:
340 | case ngx_lcb_cmd_prepend: {
341 | lcb_store_cmd_t cmd;
342 | const lcb_store_cmd_t *commands[1];
343 |
344 | commands[0] = &cmd;
345 | memset(&cmd, 0, sizeof(cmd));
346 |
347 | cmd.v.v0.operation = opcode;
348 | cmd.v.v0.key = key.data;
349 | cmd.v.v0.nkey = key.len;
350 | cmd.v.v0.bytes = val.data;
351 | cmd.v.v0.nbytes = val.len;
352 | cmd.v.v0.cas = cas;
353 | cmd.v.v0.flags = flags;
354 | cmd.v.v0.exptime = exptime;
355 | err = lcb_store(conn->lcb, r, 1, commands);
356 | ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
357 | "couchbase(%p): store request \"%*s\", operation: 0x%02xd",
358 | (void *)conn->lcb, cmd.v.v0.nkey, cmd.v.v0.key, cmd.v.v0.operation);
359 | }
360 | break;
361 | case ngx_lcb_cmd_delete: {
362 | lcb_remove_cmd_t cmd;
363 | const lcb_remove_cmd_t *commands[1];
364 |
365 | commands[0] = &cmd;
366 | memset(&cmd, 0, sizeof(cmd));
367 | cmd.v.v0.key = key.data;
368 | cmd.v.v0.nkey = key.len;
369 | err = lcb_remove(conn->lcb, r, 1, commands);
370 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
371 | "couchbase(%p): remove request \"%*s\"",
372 | (void *)conn->lcb, cmd.v.v0.nkey, cmd.v.v0.key);
373 | }
374 | break;
375 | }
376 | if (need_free_value) {
377 | ngx_pfree(r->pool, val.data);
378 | }
379 | if (err != LCB_SUCCESS) {
380 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
381 | "couchbase: failed to schedule couchbase request: %s",
382 | lcb_strerror(conn->lcb, err));
383 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
384 | return NGX_ERROR;
385 | }
386 | return NGX_OK;
387 | }
388 |
389 | static void
390 | ngx_lcb_upstream_init(ngx_http_request_t *r)
391 | {
392 | ngx_http_core_loc_conf_t *clcf;
393 | ngx_lcb_connection_t *conn;
394 | ngx_connection_t *c;
395 |
396 | c = r->connection;
397 | if (c->read->timer_set) {
398 | ngx_del_timer(c->read);
399 | }
400 | if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
401 | if (!c->write->active) {
402 | if (ngx_add_event(c->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT) == NGX_ERROR) {
403 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
404 | return;
405 | }
406 | }
407 | }
408 |
409 | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
410 | conn = ngx_http_get_couchbase_connection(clcf->name);
411 | if (conn == NULL) {
412 | ngx_log_error(NGX_LOG_ERR, c->log, 0,
413 | "couchbase: connection not found: \"%V\"", &clcf->name);
414 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
415 | return;
416 | }
417 | if (conn->connected) {
418 | /* the instance has been connected */
419 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
420 | "couchbase(%p): connection to \"%s:%s\" ready. process request",
421 | (void *)conn->lcb, lcb_get_host(conn->lcb), lcb_get_port(conn->lcb));
422 | ngx_lcb_process(r);
423 | } else {
424 | lcb_error_t err;
425 | ngx_http_request_t **rp;
426 |
427 | rp = ngx_array_push(&conn->backlog);
428 | if (rp == NULL) {
429 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
430 | return;
431 | }
432 | *rp = r;
433 | if (!conn->connect_scheduled) {
434 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
435 | "couchbase(%p): connecting to \"%s:%s\"",
436 | (void *)conn->lcb, lcb_get_host(conn->lcb), lcb_get_port(conn->lcb));
437 | err = lcb_connect(conn->lcb);
438 | if (err != LCB_SUCCESS) {
439 | ngx_log_error(NGX_LOG_EMERG, c->log, 0,
440 | "couchbase(%p): failed to initiate connection: 0x%02xd \"%s\"",
441 | conn->lcb, err, lcb_strerror(NULL, err));
442 | ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
443 | return;
444 | }
445 | conn->connect_scheduled = 1;
446 | }
447 | }
448 | }
449 |
450 |
451 | static ngx_int_t
452 | ngx_lcb_create_request(ngx_http_request_t *r)
453 | {
454 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
455 | "couchbase: create_request");
456 | r->upstream->request_bufs = NULL;
457 | return NGX_OK;
458 | }
459 |
460 | static ngx_int_t
461 | ngx_lcb_reinit_request(ngx_http_request_t *r)
462 | {
463 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
464 | "couchbase: reinit_request");
465 | return NGX_OK;
466 | }
467 |
468 | static void
469 | ngx_lcb_abort_request(ngx_http_request_t *r)
470 | {
471 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
472 | "couchbase: abort_request");
473 | }
474 |
475 | static void
476 | ngx_lcb_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
477 | {
478 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
479 | "couchbase: finalize_request");
480 | (void)rc;
481 | }
482 |
483 | static ngx_int_t
484 | ngx_lcb_process_header(ngx_http_request_t *r)
485 | {
486 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
487 | "ngx_lcb_process_header should not be called"
488 | " by the upstream");
489 | return NGX_ERROR;
490 | }
491 |
492 | static ngx_int_t
493 | ngx_lcb_input_filter_init(void *data)
494 | {
495 | ngx_http_request_t *r = data;
496 |
497 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
498 | "ngx_lcb_input_filter_init should not be called"
499 | " by the upstream");
500 | return NGX_ERROR;
501 | }
502 |
503 |
504 | static ngx_int_t
505 | ngx_lcb_input_filter(void *data, ssize_t bytes)
506 | {
507 | ngx_http_request_t *r = data;
508 |
509 | (void)bytes;
510 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
511 | "ngx_lcb_input_filter should not be called"
512 | " by the upstream");
513 | return NGX_ERROR;
514 | }
515 |
516 | static ngx_int_t
517 | ngx_lcb_handler(ngx_http_request_t *r)
518 | {
519 | ngx_int_t rc;
520 | ngx_http_upstream_t *u;
521 | ngx_lcb_loc_conf_t *llcf;
522 |
523 | llcf = ngx_http_get_module_loc_conf(r, ngx_http_couchbase_module);
524 | if (ngx_http_upstream_create(r) != NGX_OK) {
525 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
526 | }
527 |
528 | u = r->upstream;
529 | u->conf = &llcf->upstream;
530 | u->schema.len = sizeof("couchbase://") - 1;
531 | u->schema.data = (u_char *) "couchbase://";
532 | u->output.tag = (ngx_buf_tag_t) &ngx_http_couchbase_module;
533 | u->create_request = ngx_lcb_create_request;
534 | u->reinit_request = ngx_lcb_reinit_request;
535 | u->process_header = ngx_lcb_process_header;
536 | u->abort_request = ngx_lcb_abort_request;
537 | u->finalize_request = ngx_lcb_finalize_request;
538 | u->input_filter_init = ngx_lcb_input_filter_init;
539 | u->input_filter = ngx_lcb_input_filter;
540 | u->input_filter_ctx = NULL;
541 |
542 | rc = ngx_http_read_client_request_body(r, ngx_lcb_upstream_init);
543 | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
544 | ngx_http_finalize_request(r, rc);
545 | }
546 |
547 | return NGX_DONE;
548 | }
549 |
550 | static void *
551 | ngx_lcb_create_loc_conf(ngx_conf_t *cf)
552 | {
553 | ngx_lcb_loc_conf_t *conf;
554 |
555 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_lcb_loc_conf_t));
556 | if (conf == NULL) {
557 | return NULL;
558 | }
559 | conf->connect_timeout = NGX_CONF_UNSET_MSEC;
560 | conf->timeout = NGX_CONF_UNSET_MSEC;
561 |
562 | conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
563 | conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
564 | conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
565 | conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
566 | conf->upstream.intercept_errors = 1;
567 | conf->upstream.intercept_404 = 1;
568 | /*
569 | * set by ngx_pcalloc():
570 | *
571 | * conf->cmds_allowed = NULL;
572 | * conf->upstream.bufs.num = 0;
573 | * conf->upstream.next_upstream = 0;
574 | * conf->upstream.temp_path = NULL;
575 | * conf->upstream.uri = { 0, NULL };
576 | * conf->upstream.location = NULL;
577 | * conf->upstream.cyclic_temp_file = 0;
578 | * conf->upstream.buffering = 0;
579 | * conf->upstream.ignore_client_abort = 0;
580 | * conf->upstream.send_lowat = 0;
581 | * conf->upstream.bufs.num = 0;
582 | * conf->upstream.busy_buffers_size = 0;
583 | * conf->upstream.max_temp_file_size = 0;
584 | * conf->upstream.temp_file_write_size = 0;
585 | * conf->upstream.pass_request_headers = 0;
586 | * conf->upstream.pass_request_body = 0;
587 | */
588 |
589 | return conf;
590 | }
591 |
592 | static char *
593 | ngx_lcb_merge_loc_conf(ngx_conf_t *cf, void *prev, void *conf)
594 | {
595 | ngx_lcb_loc_conf_t *parent = prev;
596 | ngx_lcb_loc_conf_t *child = conf;
597 | ngx_lcb_loc_conf_t **confp;
598 | ngx_lcb_main_conf_t *cmcf;
599 |
600 | ngx_conf_merge_msec_value(child->connect_timeout, parent->connect_timeout, 2500);
601 | ngx_conf_merge_msec_value(child->timeout, parent->timeout, 2500);
602 | ngx_conf_merge_msec_value(child->upstream.connect_timeout,
603 | parent->upstream.connect_timeout, 2500);
604 | ngx_conf_merge_msec_value(child->upstream.send_timeout,
605 | parent->upstream.send_timeout, 2500);
606 | ngx_conf_merge_msec_value(child->upstream.read_timeout,
607 | parent->upstream.read_timeout, 2500);
608 | ngx_conf_merge_size_value(child->upstream.buffer_size,
609 | parent->upstream.buffer_size,
610 | (size_t) ngx_pagesize);
611 | ngx_conf_merge_bitmask_value(child->upstream.next_upstream,
612 | parent->upstream.next_upstream,
613 | (NGX_CONF_BITMASK_SET
614 | | NGX_HTTP_UPSTREAM_FT_ERROR
615 | | NGX_HTTP_UPSTREAM_FT_TIMEOUT));
616 | if (child->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
617 | child->upstream.next_upstream = NGX_CONF_BITMASK_SET
618 | | NGX_HTTP_UPSTREAM_FT_OFF;
619 | }
620 | if (child->upstream.upstream == NULL) {
621 | child->upstream.upstream = parent->upstream.upstream;
622 | }
623 |
624 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_couchbase_module);
625 | if (child->name.data) {
626 | confp = ngx_array_push(&cmcf->connection_confs);
627 | *confp = child;
628 | }
629 | return NGX_CONF_OK;
630 | }
631 |
632 | /* parse couchbase_pass arguments.
633 | * full form is:
634 | *
635 | * couchbase_pass host:port bucket=val user=val password=val
636 | */
637 | static char *
638 | ngx_lcb_scan_options(ngx_conf_t *cf, struct lcb_create_st *options)
639 | {
640 | ngx_str_t *value;
641 | size_t ii, len;
642 | char *ptr;
643 |
644 | if (cf->args->nelts < 1) {
645 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
646 | "couchbase: address argument required for couchbase_pass");
647 | return NGX_CONF_ERROR;
648 | }
649 | options->version = 0;
650 |
651 | value = cf->args->elts;
652 | ii = 1;
653 | ptr = ngx_pcalloc(cf->pool, sizeof(char) * (value[1].len + 1));
654 | if (ptr == NULL) {
655 | goto nomem;
656 | }
657 | /* HACK nginx has special meaning for ';' therefore we are using
658 | * comma as separator for multiple bootstrap hosts. */
659 | for (ii = 0; ii < value[1].len; ++ii) {
660 | ptr[ii] = value[1].data[ii];
661 | if (ptr[ii] == ',') {
662 | ptr[ii] = ';';
663 | }
664 | }
665 | options->v.v0.host = ptr;
666 | /* optional arguments */
667 | for (ii = 2; ii < cf->args->nelts; ii++) {
668 |
669 | if (ngx_strncmp(value[ii].data, "bucket=", sizeof("bucket=") - 1) == 0) {
670 | len = value[ii].len - (sizeof("bucket=") - 1);
671 | ptr = ngx_pcalloc(cf->pool, sizeof(char) * (len + 1));
672 | if (ptr == NULL) {
673 | goto nomem;
674 | }
675 | ngx_memcpy(ptr, &value[ii].data[sizeof("bucket=") - 1], len);
676 | options->v.v0.bucket = ptr;
677 | continue;
678 | }
679 |
680 | if (ngx_strncmp(value[ii].data, "user=", sizeof("user=") - 1) == 0) {
681 | len = value[ii].len - (sizeof("user=") - 1);
682 | ptr = ngx_pcalloc(cf->pool, sizeof(char) * (len + 1));
683 | if (ptr == NULL) {
684 | goto nomem;
685 | }
686 | ngx_memcpy(ptr, &value[ii].data[sizeof("user=") - 1], len);
687 | options->v.v0.user = ptr;
688 | continue;
689 | }
690 |
691 | if (ngx_strncmp(value[ii].data, "password=", sizeof("password=") - 1) == 0) {
692 | len = value[ii].len - (sizeof("password=") - 1);
693 | ptr = ngx_pcalloc(cf->pool, sizeof(char) * (len + 1));
694 | if (ptr == NULL) {
695 | goto nomem;
696 | }
697 | ngx_memcpy(ptr, &value[ii].data[sizeof("password=") - 1], len);
698 | options->v.v0.passwd = ptr;
699 | continue;
700 | }
701 |
702 | goto invalid;
703 | }
704 | return NGX_CONF_OK;
705 |
706 | nomem:
707 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
708 | "couchbase: failed to allocate memory for \"%V\" in %s:%ui", &value[ii]);
709 | return NGX_CONF_ERROR;
710 |
711 | invalid:
712 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
713 | "couchbase: invalid parameter \"%V\"", &value[ii]);
714 | return NGX_CONF_ERROR;
715 | }
716 |
717 |
718 | static char *
719 | ngx_lcb_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
720 | {
721 | ngx_lcb_loc_conf_t *ccf = conf;
722 | ngx_http_core_loc_conf_t *clcf;
723 | char *rc;
724 |
725 | if (ccf->name.data) {
726 | return "is duplicate";
727 | }
728 | ngx_lcb_enabled = 1;
729 |
730 | rc = ngx_lcb_scan_options(cf, &ccf->options);
731 | if (rc != NGX_CONF_OK) {
732 | return rc;
733 | }
734 |
735 | clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
736 | clcf->handler = ngx_lcb_handler;
737 | if (clcf->name.data[clcf->name.len - 1] == '/') {
738 | clcf->auto_redirect = 1;
739 | }
740 | ccf->name = clcf->name;
741 |
742 | (void)cmd;
743 | return NGX_CONF_OK;
744 | }
745 |
746 | static ngx_int_t
747 | ngx_lcb_variable_not_found(ngx_http_request_t *r,
748 | ngx_http_variable_value_t *v,
749 | uintptr_t data)
750 | {
751 | v->not_found = 1;
752 | (void)r;
753 | (void)data;
754 | return NGX_OK;
755 | }
756 |
757 | static ngx_int_t
758 | ngx_lcb_add_variable(ngx_conf_t *cf, ngx_str_t *name)
759 | {
760 | ngx_http_variable_t *v;
761 |
762 | v = ngx_http_add_variable(cf, name, NGX_HTTP_VAR_CHANGEABLE);
763 | if (v == NULL) {
764 | return NGX_ERROR;
765 | }
766 |
767 | v->get_handler = ngx_lcb_variable_not_found;
768 |
769 | return ngx_http_get_variable_index(cf, name);
770 | }
771 |
772 | static ngx_int_t
773 | ngx_lcb_postconf(ngx_conf_t *cf)
774 | {
775 | if (!ngx_lcb_enabled) {
776 | return NGX_OK;
777 | }
778 |
779 | ngx_lcb_cmd_idx = ngx_lcb_add_variable(cf, &ngx_lcb_cmd);
780 | if (ngx_lcb_cmd_idx == NGX_ERROR) {
781 | return NGX_ERROR;
782 | }
783 | ngx_lcb_key_idx = ngx_lcb_add_variable(cf, &ngx_lcb_key);
784 | if (ngx_lcb_key_idx == NGX_ERROR) {
785 | return NGX_ERROR;
786 | }
787 | ngx_lcb_val_idx = ngx_lcb_add_variable(cf, &ngx_lcb_val);
788 | if (ngx_lcb_val_idx == NGX_ERROR) {
789 | return NGX_ERROR;
790 | }
791 | ngx_lcb_cas_idx = ngx_lcb_add_variable(cf, &ngx_lcb_cas);
792 | if (ngx_lcb_cas_idx == NGX_ERROR) {
793 | return NGX_ERROR;
794 | }
795 | ngx_lcb_flags_idx = ngx_lcb_add_variable(cf, &ngx_lcb_flags);
796 | if (ngx_lcb_flags_idx == NGX_ERROR) {
797 | return NGX_ERROR;
798 | }
799 | ngx_lcb_exptime_idx = ngx_lcb_add_variable(cf, &ngx_lcb_exptime);
800 | if (ngx_lcb_exptime_idx == NGX_ERROR) {
801 | return NGX_ERROR;
802 | }
803 | return NGX_OK;
804 | }
805 |
806 | void
807 | ngx_lcb_error_callback(lcb_t instance, lcb_error_t error, const char *errinfo)
808 | {
809 | ngx_log_error(NGX_LOG_EMERG, lcb_cookie.log, 0,
810 | "couchbase(%p): general error: %s (0x%xd), %s", instance,
811 | lcb_strerror(instance, error), error, errinfo);
812 | exit(-1);
813 | }
814 |
815 | static ngx_int_t
816 | ngx_lcb_init_process(ngx_cycle_t *cycle)
817 | {
818 | ngx_lcb_main_conf_t *cmcf;
819 | struct lcb_create_io_ops_st options;
820 | lcb_error_t err;
821 | ngx_int_t rc;
822 | ngx_uint_t i;
823 | ngx_lcb_connection_t *conn;
824 | ngx_lcb_loc_conf_t **ccfp;
825 |
826 | /* initialize libcouchbase IO plugin */
827 | memset(&options, 0, sizeof(options));
828 | options.version = 2;
829 | options.v.v2.create = ngx_lcb_create_io_opts;
830 | options.v.v2.cookie = &lcb_cookie;
831 | err = lcb_create_io_ops(&lcb_cookie.io, &options);
832 | if (err != LCB_SUCCESS) {
833 | ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
834 | "couchbase: failed to create IO object for libcouchbase: 0x%02xd \"%s\"",
835 | err, lcb_strerror(NULL, err));
836 | return NGX_ERROR;
837 | }
838 |
839 | lcb_cookie.log = cycle->log;
840 | lcb_cookie.pool = cycle->pool;
841 |
842 | /* materialize upstream connections */
843 | rc = ngx_array_init(&lcb_connections, cycle->pool, 4, sizeof(ngx_lcb_connection_t));
844 | if (rc != NGX_OK) {
845 | return NGX_ERROR;
846 | }
847 | cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_couchbase_module);
848 | ccfp = cmcf->connection_confs.elts;
849 | for (i = 0; i < cmcf->connection_confs.nelts; i++) {
850 | struct lcb_create_st opts = ccfp[i]->options;
851 |
852 | conn = ngx_array_push(&lcb_connections);
853 | if (conn == NULL) {
854 | return NGX_ERROR;
855 | }
856 | conn->log = cycle->log;
857 | conn->name = ccfp[i]->name;
858 | rc = ngx_array_init(&conn->backlog, cycle->pool, 4, sizeof(ngx_http_request_t *));
859 | if (rc != NGX_OK) {
860 | return NGX_ERROR;
861 | }
862 | opts.v.v0.io = lcb_cookie.io;
863 | err = lcb_create(&conn->lcb, &opts);
864 | if (err != LCB_SUCCESS) {
865 | ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
866 | "couchbase: failed to create libcouchbase instance: 0x%02xd \"%s\"",
867 | err, lcb_strerror(NULL, err));
868 | return NGX_ERROR;
869 | }
870 | (void)lcb_set_error_callback(conn->lcb, ngx_lcb_error_callback);
871 | (void)lcb_set_timeout(conn->lcb, ccfp[i]->connect_timeout * 1000); /* in usec */
872 | (void)lcb_set_get_callback(conn->lcb, ngx_lcb_get_callback);
873 | (void)lcb_set_store_callback(conn->lcb, ngx_lcb_store_callback);
874 | (void)lcb_set_remove_callback(conn->lcb, ngx_lcb_remove_callback);
875 | (void)lcb_set_configuration_callback(conn->lcb, ngx_lcb_configuration_callback);
876 | (void)lcb_set_cookie(conn->lcb, conn);
877 | ngx_log_debug7(NGX_LOG_DEBUG_HTTP, cycle->log, 0,
878 | "couchbase(%p): configured connection \"%V\": connect_timeout:%Mms "
879 | "address:%s bucket:%s user:%s password:%s",
880 | conn->lcb, &conn->name, ccfp[i]->connect_timeout,
881 | opts.v.v0.host ? opts.v.v0.host : "(null)",
882 | opts.v.v0.bucket ? opts.v.v0.bucket : "(null)",
883 | opts.v.v0.user ? opts.v.v0.user : "(null)",
884 | opts.v.v0.passwd ? opts.v.v0.passwd : "(null)");
885 | }
886 | return NGX_OK;
887 | }
888 |
889 | static void
890 | ngx_lcb_exit_process(ngx_cycle_t *cycle)
891 | {
892 | lcb_destroy_io_ops(lcb_cookie.io);
893 | (void)cycle;
894 | }
895 |
896 | static void *
897 | ngx_lcb_create_main_conf(ngx_conf_t *cf)
898 | {
899 | ngx_lcb_main_conf_t *cmcf;
900 | ngx_int_t rc;
901 |
902 | cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_lcb_main_conf_t));
903 | if (cmcf == NULL) {
904 | return NULL;
905 | }
906 |
907 | rc = ngx_array_init(&cmcf->connection_confs, cf->pool, 4,
908 | sizeof(ngx_lcb_loc_conf_t *));
909 | if (rc != NGX_OK) {
910 | return NULL;
911 | }
912 |
913 | return cmcf;
914 | }
915 |
916 | static ngx_lcb_connection_t *
917 | ngx_http_get_couchbase_connection(ngx_str_t name)
918 | {
919 | ngx_lcb_connection_t *conn;
920 | ngx_uint_t i;
921 |
922 | conn = lcb_connections.elts;
923 | for (i = 0; i < lcb_connections.nelts; i++) {
924 | if (name.len == conn[i].name.len &&
925 | ngx_strncmp(name.data, conn[i].name.data, name.len) == 0) {
926 | return &conn[i];
927 | }
928 | }
929 |
930 | return NULL;
931 | }
932 |
--------------------------------------------------------------------------------
/src/ngx_lcb_module.h:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 | /*
3 | * Copyright 2013 Couchbase, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | #ifndef NGX_HTTP_COUCHBASE_MODULE_H
19 | #define NGX_HTTP_COUCHBASE_MODULE_H
20 |
21 | #include
22 | #include
23 | #include
24 |
25 | #include "ngx_lcb_callbacks.h"
26 | #include "ngx_lcb_plugin.h"
27 |
28 | typedef struct ngx_lcb_loc_conf_s {
29 | ngx_str_t name;
30 | ngx_msec_t connect_timeout;
31 | ngx_msec_t timeout;
32 | struct lcb_create_st options;
33 | ngx_http_upstream_conf_t upstream;
34 | } ngx_lcb_loc_conf_t;
35 |
36 | typedef struct {
37 | ngx_array_t connection_confs; /* ngx_lcb_loc_conf_t */
38 | } ngx_lcb_main_conf_t;
39 |
40 | extern ngx_module_t ngx_http_couchbase_module;
41 |
42 | extern ngx_int_t ngx_lcb_cmd_idx;
43 | extern ngx_int_t ngx_lcb_key_idx;
44 | extern ngx_int_t ngx_lcb_val_idx;
45 | extern ngx_int_t ngx_lcb_cas_idx;
46 | extern ngx_int_t ngx_lcb_flags_idx;
47 | extern ngx_int_t ngx_lcb_exptime_idx;
48 |
49 | ngx_int_t ngx_lcb_process(ngx_http_request_t *r);
50 |
51 | typedef struct ngx_lcb_connection_s {
52 | ngx_str_t name;
53 | lcb_t lcb;
54 | ngx_log_t *log;
55 | ngx_array_t backlog; /* the list of postponed (ngx_http_request_t *) */
56 | unsigned connected:1;
57 | unsigned connect_scheduled:1;
58 | } ngx_lcb_connection_t;
59 |
60 | #endif /* NGX_HTTP_COUCHBASE_MODULE_H */
61 |
62 |
--------------------------------------------------------------------------------
/src/ngx_lcb_plugin.c:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 | /*
3 | * Copyright 2013 Couchbase, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * This file contains IO operations that use nginx
20 | *
21 | * @author Sergey Avseyev
22 | */
23 |
24 | #ifndef DDEBUG
25 | #define DDEBUG 0
26 | #endif
27 | #include "ddebug.h"
28 |
29 | #include "ngx_lcb_module.h"
30 |
31 | typedef void (*ngx_lcb_handler_pt)(lcb_socket_t sock, short which, void *data);
32 |
33 | struct ngx_lcb_context_s {
34 | ngx_peer_connection_t *peer;
35 | ngx_lcb_handler_pt handler;
36 | short handler_mask;
37 | void *handler_data;
38 | struct addrinfo *root_ai;
39 | struct addrinfo *curr_ai;
40 | lcb_io_plugin_connect_cb conn_handler;
41 | void *conn_data;
42 | ngx_chain_t *io_chains;
43 | ngx_buf_t *io_bufs;
44 | lcb_uint32_t io_len;
45 | };
46 | typedef struct ngx_lcb_context_s ngx_lcb_context_t;
47 |
48 | #define to_socket(X) ((lcb_socket_t)(intptr_t)(X))
49 | #define from_socket(X) ((ngx_lcb_context_t *)(intptr_t)(X))
50 |
51 | /* XXX it should use instance-level setting somehow */
52 | static int common_getaddrinfo(const char *hostname,
53 | const char *servname,
54 | struct addrinfo **res)
55 | {
56 | struct addrinfo hints;
57 | memset(&hints, 0, sizeof(hints));
58 | hints.ai_flags = AI_PASSIVE;
59 | hints.ai_socktype = SOCK_STREAM;
60 | hints.ai_family = AF_INET;
61 | return getaddrinfo(hostname, servname, &hints, res);
62 | }
63 |
64 | static void
65 | ngx_lcb_close(lcb_io_opt_t io, lcb_socket_t sock);
66 |
67 | /* allocate ngx_peer_connection_t struct */
68 | static lcb_socket_t
69 | ngx_lcb_socket(lcb_io_opt_t io, const char *hostname, const char *servname)
70 | {
71 | ngx_lcb_context_t *ctx;
72 | ngx_lcb_cookie_t cookie = io->v.v0.cookie;
73 |
74 | ctx = ngx_pcalloc(cookie->pool, sizeof(ngx_lcb_context_t));
75 | if (ctx == NULL) {
76 | return to_socket(-1);
77 | }
78 | ctx->io_len = io->v.v0.iov_max;
79 | ctx->io_chains = ngx_pcalloc(cookie->pool, sizeof(ngx_chain_t) * ctx->io_len);
80 | ctx->io_bufs = ngx_pcalloc(cookie->pool, sizeof(ngx_buf_t) * ctx->io_len);
81 | ctx->peer = ngx_pcalloc(cookie->pool, sizeof(ngx_peer_connection_t));
82 | if (ctx->io_chains == NULL || ctx->io_bufs == NULL || ctx->peer == NULL) {
83 | ngx_lcb_close(io, to_socket(ctx));
84 | return to_socket(-1);
85 | }
86 | ctx->peer->log = cookie->log;
87 | ctx->peer->log_error = NGX_ERROR_ERR;
88 | ctx->peer->get = ngx_event_get_peer;
89 | if (common_getaddrinfo(hostname, servname, &ctx->root_ai) != 0) {
90 | ngx_lcb_close(io, to_socket(ctx));
91 | return to_socket(-1);
92 | }
93 | ctx->curr_ai = ctx->root_ai;
94 |
95 | return to_socket(ctx);
96 | }
97 |
98 | static void
99 | ngx_lcb_close(lcb_io_opt_t io, lcb_socket_t sock)
100 | {
101 | ngx_lcb_cookie_t cookie = io->v.v0.cookie;
102 | ngx_lcb_context_t *ctx = from_socket(sock);
103 |
104 | if (ctx->peer->connection != NULL) {
105 | ngx_close_connection(ctx->peer->connection);
106 | ctx->peer->connection = NULL;
107 | }
108 | ngx_pfree(cookie->pool, ctx->peer);
109 | ngx_pfree(cookie->pool, ctx->io_chains);
110 | ngx_pfree(cookie->pool, ctx->io_bufs);
111 | ngx_pfree(cookie->pool, ctx);
112 | }
113 |
114 | static void ngx_lcb_handler_thunk(ngx_event_t *ev)
115 | {
116 | ngx_connection_t *conn = ev->data;
117 | ngx_lcb_context_t *ctx = conn->data;
118 | int which = 0;
119 |
120 | if (ev->write) {
121 | which |= LCB_WRITE_EVENT;
122 | } else {
123 | which |= LCB_READ_EVENT;
124 | }
125 |
126 | if (ctx->handler_mask & which) {
127 | ctx->handler(to_socket(ctx), which, ctx->handler_data);
128 | }
129 | }
130 |
131 | LIBCOUCHBASE_API
132 | int ngx_lcb_connect(lcb_io_opt_t io,
133 | lcb_socket_t sock,
134 | const struct sockaddr *name,
135 | unsigned int namelen)
136 | {
137 | ngx_lcb_cookie_t cookie = io->v.v0.cookie;
138 | ngx_lcb_context_t *ctx = from_socket(sock);
139 | ngx_peer_connection_t *peer = ctx->peer;
140 | size_t len;
141 |
142 | peer->sockaddr = (struct sockaddr *)name;
143 | peer->socklen = namelen;
144 | /* FIXME free peer->name later */
145 | peer->name = ngx_pnalloc(cookie->pool, sizeof(ngx_str_t));
146 | if (peer->name == NULL) {
147 | return -1;
148 | }
149 | len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
150 | peer->name->data = ngx_pnalloc(cookie->pool, len);
151 | if (peer->name->data == NULL) {
152 | ngx_pfree(cookie->pool, peer->name);
153 | return -1;
154 | }
155 | peer->name->len = ngx_sock_ntop(peer->sockaddr, peer->name->data, len, 1);
156 | return 0;
157 | }
158 |
159 | static void ngx_lcb_connect_handler_thunk(ngx_event_t *ev)
160 | {
161 | ngx_connection_t *conn = ev->data;
162 | ngx_lcb_context_t *ctx = conn->data;
163 |
164 | conn->read->handler = ngx_lcb_handler_thunk;
165 | conn->write->handler = ngx_lcb_handler_thunk;
166 | ctx->conn_handler(LCB_SUCCESS, ctx->conn_data);
167 |
168 | /* check for broken connection */
169 | }
170 |
171 | LIBCOUCHBASE_API
172 | void ngx_lcb_connect_peer(lcb_io_opt_t io,
173 | lcb_socket_t sock,
174 | void *event,
175 | void *cb_data,
176 | lcb_io_plugin_connect_cb handler)
177 | {
178 | ngx_lcb_context_t *ctx = from_socket(sock);
179 | ngx_peer_connection_t *peer = ctx->peer;
180 | ngx_int_t rc;
181 |
182 | if (peer->sockaddr == NULL) {
183 | if (ngx_lcb_connect(io, sock, ctx->curr_ai->ai_addr,
184 | ctx->curr_ai->ai_addrlen) != 0) {
185 | handler(LCB_CONNECT_ERROR, cb_data);
186 | return;
187 | }
188 | }
189 | rc = ngx_event_connect_peer(peer);
190 | if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
191 | io->v.v0.error = ngx_socket_errno;
192 | handler(LCB_CONNECT_ERROR, cb_data);
193 | return;
194 | }
195 | peer->connection->data = ctx;
196 | if (rc == NGX_AGAIN) {
197 | peer->connection->write->handler = ngx_lcb_connect_handler_thunk;
198 | peer->connection->read->handler = ngx_lcb_connect_handler_thunk;
199 | ctx->conn_data = cb_data;
200 | ctx->conn_handler = handler;
201 | ngx_add_timer(peer->connection->write, 300); /* ms */
202 | return;
203 | }
204 | peer->connection->read->handler = ngx_lcb_handler_thunk;
205 | peer->connection->write->handler = ngx_lcb_handler_thunk;
206 | handler(LCB_SUCCESS, cb_data);
207 | (void)event;
208 | }
209 |
210 | static lcb_ssize_t
211 | ngx_lcb_recv(lcb_io_opt_t io, lcb_socket_t sock, void *buf, lcb_size_t nbuf, int flags)
212 | {
213 | ngx_lcb_context_t *ctx = from_socket(sock);
214 | ssize_t ret;
215 |
216 | ret = ctx->peer->connection->recv(ctx->peer->connection, buf, nbuf);
217 | if (ret < 0) {
218 | io->v.v0.error = ngx_socket_errno;
219 | }
220 |
221 | (void)flags;
222 | return ret;
223 | }
224 |
225 | static ngx_chain_t *
226 | iovec2chain(ngx_lcb_context_t *ctx, struct lcb_iovec_st *iov,
227 | lcb_size_t niov, int recv)
228 | {
229 | ngx_chain_t *cc = ctx->io_chains;
230 | ngx_buf_t *bb = ctx->io_bufs;
231 | lcb_size_t ii;
232 |
233 | if (niov > ctx->io_len) {
234 | niov = ctx->io_len;
235 | }
236 | for (ii = 0; ii < niov && iov[ii].iov_len != 0; ++ii) {
237 | bb[ii].start = (u_char *)iov[ii].iov_base;
238 | bb[ii].end = (u_char *)iov[ii].iov_base + iov[ii].iov_len;
239 | bb[ii].pos = bb[ii].start;
240 | bb[ii].last = recv ? bb[ii].start : bb[ii].end;
241 | bb[ii].memory = 1;
242 | cc[ii].buf = bb + ii;
243 | cc[ii].next = cc + ii + 1;
244 | }
245 | cc[ii - 1].next = NULL;
246 | bb[ii - 1].last_buf = 1;
247 |
248 | return cc;
249 | }
250 |
251 | static lcb_ssize_t
252 | ngx_lcb_recvv(lcb_io_opt_t io, lcb_socket_t sock, struct lcb_iovec_st *iov, lcb_size_t niov)
253 | {
254 | ngx_lcb_context_t *ctx = from_socket(sock);
255 | ngx_chain_t *chain;
256 | ssize_t ret;
257 |
258 | chain = iovec2chain(ctx, iov, niov, 1);
259 | ret = ctx->peer->connection->recv_chain(ctx->peer->connection, chain);
260 | if (ret < 0) {
261 | io->v.v0.error = ngx_socket_errno;
262 | }
263 | return ret;
264 | }
265 |
266 | static lcb_ssize_t
267 | ngx_lcb_send(lcb_io_opt_t io, lcb_socket_t sock, const void *buf, lcb_size_t nbuf, int flags)
268 | {
269 | ngx_lcb_context_t *ctx = from_socket(sock);
270 | ssize_t ret;
271 |
272 | ret = ctx->peer->connection->send(ctx->peer->connection, (u_char *)buf, nbuf);
273 | if (ret < 0) {
274 | io->v.v0.error = ngx_socket_errno;
275 | }
276 |
277 | (void)flags;
278 | return ret;
279 | }
280 |
281 | static lcb_ssize_t
282 | ngx_lcb_sendv(lcb_io_opt_t io, lcb_socket_t sock, struct lcb_iovec_st *iov, lcb_size_t niov)
283 | {
284 | ngx_lcb_context_t *ctx = from_socket(sock);
285 | ngx_chain_t *chain, *rc;
286 | ssize_t old;
287 |
288 | old = ctx->peer->connection->sent;
289 | chain = iovec2chain(ctx, iov, niov, 0);
290 | rc = ctx->peer->connection->send_chain(ctx->peer->connection, chain, 0);
291 | if (rc == NGX_CHAIN_ERROR) {
292 | io->v.v0.error = ngx_socket_errno;
293 | }
294 | return ctx->peer->connection->sent - old;
295 | }
296 |
297 | static void
298 | ngx_lcb_timer_thunk(ngx_event_t *ev)
299 | {
300 | ngx_lcb_context_t *ctx = ev->data;
301 |
302 | ctx->handler(to_socket(-1), 0, ctx->handler_data);
303 | }
304 |
305 | static int
306 | ngx_lcb_timer_update(lcb_io_opt_t io, void *timer, lcb_uint32_t usec, void *data, ngx_lcb_handler_pt handler)
307 | {
308 | ngx_event_t *tm = timer;
309 | ngx_lcb_context_t *ctx;
310 |
311 | ctx = tm->data;
312 | ctx->handler = handler;
313 | ctx->handler_data = data;
314 | ngx_add_timer(tm, usec / 1000);
315 | (void)io;
316 | return 0;
317 | }
318 |
319 | void
320 | ngx_lcb_timer_delete(lcb_io_opt_t io, void *timer)
321 | {
322 | ngx_event_t *tm = timer;
323 |
324 | if (tm->timer_set) {
325 | ngx_del_timer(tm);
326 | }
327 | (void)io;
328 | }
329 |
330 | static void *
331 | ngx_lcb_timer_create(lcb_io_opt_t io)
332 | {
333 | ngx_lcb_cookie_t cookie = io->v.v0.cookie;
334 | ngx_event_t *tm;
335 | ngx_lcb_context_t *ctx;
336 |
337 | tm = ngx_pcalloc(cookie->pool, sizeof(ngx_event_t));
338 | if (tm == NULL) {
339 | return NULL;
340 | }
341 | ctx = ngx_pcalloc(cookie->pool, sizeof(ngx_lcb_context_t));
342 | if (ctx == NULL) {
343 | ngx_pfree(cookie->pool, tm);
344 | return NULL;
345 | }
346 | tm->handler = ngx_lcb_timer_thunk;
347 | tm->data = ctx;
348 | tm->log = cookie->log;
349 | return tm;
350 | }
351 |
352 | static void
353 | ngx_lcb_timer_destroy(lcb_io_opt_t io, void *timer)
354 | {
355 | ngx_lcb_cookie_t cookie = io->v.v0.cookie;
356 | ngx_event_t *tm = timer;
357 |
358 | ngx_pfree(cookie->pool, tm);
359 | }
360 |
361 | static int
362 | ngx_lcb_event_update(lcb_io_opt_t io, lcb_socket_t sock, void *event, short flags, void *data, ngx_lcb_handler_pt handler)
363 | {
364 | ngx_lcb_context_t *ctx = from_socket(sock);
365 | ngx_int_t f;
366 |
367 | ctx->handler = handler;
368 | ctx->handler_mask = flags;
369 | ctx->handler_data = data;
370 |
371 | if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
372 | /* kqueue */
373 | f = NGX_CLEAR_EVENT;
374 | } else {
375 | /* select, poll, /dev/poll */
376 | f = NGX_LEVEL_EVENT;
377 | }
378 | /* FIXME check return value */
379 | if (flags & LCB_WRITE_EVENT) {
380 | ngx_add_event(ctx->peer->connection->write, NGX_WRITE_EVENT, f);
381 | }
382 | if (flags & LCB_READ_EVENT) {
383 | ngx_add_event(ctx->peer->connection->read, NGX_READ_EVENT, f);
384 | }
385 |
386 | (void)io;
387 | (void)event;
388 | return 0;
389 | }
390 |
391 | static void
392 | ngx_lcb_event_delete(lcb_io_opt_t io, lcb_socket_t sock, void *event)
393 | {
394 | ngx_lcb_context_t *ctx = from_socket(sock);
395 |
396 | ctx->handler = NULL;
397 | ctx->handler_mask = 0;
398 | ctx->handler_data = NULL;
399 | (void)event;
400 | (void)io;
401 | }
402 |
403 | static void *
404 | ngx_lcb_event_create_noop(lcb_io_opt_t io)
405 | {
406 | (void)io;
407 | return (void *)0xdeadbeef;
408 | }
409 |
410 | static void
411 | ngx_lcb_event_destroy_noop(lcb_io_opt_t io, void *event)
412 | {
413 | (void)io;
414 | (void)event;
415 | }
416 |
417 | static void
418 | ngx_lcb_noop(lcb_io_opt_t io)
419 | {
420 | ngx_lcb_cookie_t cookie = io->v.v0.cookie;
421 | (void)cookie;
422 | }
423 |
424 | static void
425 | ngx_lcb_destroy_io_opts(lcb_io_opt_t io)
426 | {
427 | free(io);
428 | }
429 |
430 | lcb_error_t
431 | ngx_lcb_create_io_opts(int version, lcb_io_opt_t *io, void *cookie)
432 | {
433 | lcb_io_opt_t ret;
434 |
435 | if (version != 0) {
436 | return LCB_PLUGIN_VERSION_MISMATCH;
437 | }
438 | ret = calloc(1, sizeof(*ret));
439 | if (ret == NULL) {
440 | free(ret);
441 | return LCB_CLIENT_ENOMEM;
442 | }
443 | if (cookie == NULL) {
444 | return LCB_EINVAL;
445 | } else {
446 | ret->v.v0.cookie = cookie;
447 | }
448 |
449 | /* setup io iops! */
450 | ret->version = 0;
451 | ret->dlhandle = NULL;
452 | ret->destructor = ngx_lcb_destroy_io_opts;
453 | /* consider that struct isn't allocated by the library,
454 | * `need_cleanup' flag might be set in lcb_create() */
455 | ret->v.v0.need_cleanup = 0;
456 | ret->v.v0.recv = ngx_lcb_recv;
457 | ret->v.v0.send = ngx_lcb_send;
458 | ret->v.v0.recvv = ngx_lcb_recvv;
459 | ret->v.v0.sendv = ngx_lcb_sendv;
460 | ret->v.v0.socket = ngx_lcb_socket;
461 | ret->v.v0.close = ngx_lcb_close;
462 | ret->v.v0.connect = ngx_lcb_connect;
463 | ret->v.v0.connect_peer = ngx_lcb_connect_peer;
464 |
465 | ret->v.v0.delete_event = ngx_lcb_event_delete;
466 | ret->v.v0.update_event = ngx_lcb_event_update;
467 |
468 | ret->v.v0.create_timer = ngx_lcb_timer_create;
469 | ret->v.v0.destroy_timer = ngx_lcb_timer_destroy;
470 | ret->v.v0.delete_timer = ngx_lcb_timer_delete;
471 | ret->v.v0.update_timer = ngx_lcb_timer_update;
472 |
473 | ret->v.v0.create_event = ngx_lcb_event_create_noop;
474 | ret->v.v0.destroy_event = ngx_lcb_event_destroy_noop;
475 | ret->v.v0.run_event_loop = ngx_lcb_noop;
476 | ret->v.v0.stop_event_loop = ngx_lcb_noop;
477 | #if (IOV_MAX > 64)
478 | ret->v.v0.iov_max = 64;
479 | #else
480 | ret->v.v0.iov_max = IOV_MAX;
481 | #endif
482 |
483 | *io = ret;
484 | return LCB_SUCCESS;
485 | }
486 |
--------------------------------------------------------------------------------
/src/ngx_lcb_plugin.h:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 | /*
3 | * Copyright 2013 Couchbase, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | #ifndef NGX_LCB_PLUGIN_H
19 | #define NGX_LCB_PLUGIN_H 1
20 |
21 | struct ngx_lcb_cookie_s {
22 | lcb_io_opt_t io;
23 | ngx_log_t *log;
24 | ngx_pool_t *pool;
25 | };
26 | typedef struct ngx_lcb_cookie_s *ngx_lcb_cookie_t;
27 |
28 | lcb_error_t ngx_lcb_create_io_opts(int version, lcb_io_opt_t *io, void *cookie);
29 |
30 | #endif
31 |
32 |
--------------------------------------------------------------------------------
/t/smoke.t:
--------------------------------------------------------------------------------
1 | # vi:filetype=perl
2 |
3 | use lib 'lib';
4 | use Test::Nginx::Socket;
5 |
6 | $ENV{TEST_NGINX_COUCHBASE_HOST} ||= '127.0.0.1:8091';
7 |
8 | plan tests => 68;
9 | run_tests();
10 |
11 | __DATA__
12 |
13 | === TEST 1: set only
14 | --- config
15 | location /cb {
16 | set $couchbase_cmd $arg_cmd;
17 | set $couchbase_key $arg_key;
18 | set $couchbase_val $arg_val;
19 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
20 | }
21 | --- request eval
22 | my $key = "test1_" . time();
23 | "GET /cb?cmd=set&key=$key&val=blah"
24 | --- error_code: 201
25 | --- response_body
26 |
27 | === TEST 2: add only
28 | --- config
29 | location /cb {
30 | set $couchbase_cmd $arg_cmd;
31 | set $couchbase_key $arg_key;
32 | set $couchbase_val $arg_val;
33 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
34 | }
35 | --- request eval
36 | my $key = "test2_" . time();
37 | "GET /cb?cmd=add&key=$key&val=blah"
38 | --- error_code: 201
39 | --- response_body
40 |
41 | === TEST 3: set and get
42 | --- config
43 | location /cb {
44 | set $couchbase_cmd $arg_cmd;
45 | set $couchbase_key $arg_key;
46 | set $couchbase_val $arg_val;
47 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
48 | }
49 | --- request eval
50 | my $key = "test3_" . time();
51 | [
52 | "GET /cb?cmd=set&key=$key&val=blah",
53 | "GET /cb?cmd=get&key=$key"
54 | ]
55 | --- error_code eval
56 | [
57 | 201,
58 | 200
59 | ]
60 | --- response_body eval
61 | [
62 | "",
63 | "blah"
64 | ]
65 |
66 | === TEST 4: set and delete
67 | --- config
68 | location /cb {
69 | set $couchbase_cmd $arg_cmd;
70 | set $couchbase_key $arg_key;
71 | set $couchbase_val $arg_val;
72 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
73 | }
74 | --- request eval
75 | my $key = "test4_" . time();
76 | [
77 | "GET /cb?cmd=set&key=$key&val=blah",
78 | "GET /cb?cmd=delete&key=$key"
79 | ]
80 | --- error_code eval
81 | [
82 | 201,
83 | 200
84 | ]
85 | --- response_body eval
86 | [
87 | "",
88 | ""
89 | ]
90 |
91 | === TEST 5: add after set
92 | --- config
93 | location /cb {
94 | set $couchbase_cmd $arg_cmd;
95 | set $couchbase_key $arg_key;
96 | set $couchbase_val $arg_val;
97 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
98 | }
99 | --- request eval
100 | my $key = "test5_" . time();
101 | [
102 | "GET /cb?cmd=set&key=$key&val=blah",
103 | "GET /cb?cmd=add&key=$key&val=blah2",
104 | "GET /cb?cmd=get&key=$key",
105 | ]
106 | --- error_code eval
107 | [
108 | 201,
109 | 409,
110 | 200
111 | ]
112 | --- response_body eval
113 | [
114 | "",
115 | '{"error":"key_eexists","reason":"Key exists (with a different CAS value)"}',
116 | "blah"
117 | ]
118 |
119 | === TEST 6: handle URL encoding properly
120 | --- config
121 | location /cb {
122 | set $couchbase_cmd $arg_cmd;
123 | set $couchbase_key $arg_key;
124 | set $couchbase_val $arg_val;
125 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
126 | }
127 | --- request eval
128 | my $key = "test6_" . time();
129 | [
130 | "GET /cb?cmd=set&key=$key&val=foo%20bar",
131 | "GET /cb?cmd=get&key=$key"
132 | ]
133 | --- error_code eval
134 | [
135 | 201,
136 | 200
137 | ]
138 | --- response_body eval
139 | [
140 | "",
141 | "foo bar"
142 | ]
143 |
144 | === TEST 7: use REST-like interface
145 | --- config
146 | location /cb {
147 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
148 | }
149 | --- request eval
150 | my $key = "test7_" . time();
151 | [
152 | "POST /cb/$key\n\r\n\rhello world",
153 | "GET /cb/$key",
154 | "PUT /cb/$key\n\r\n\rHello, world!",
155 | "GET /cb/$key",
156 | "DELETE /cb/$key",
157 | ]
158 | --- error_code eval
159 | [
160 | 201,
161 | 200,
162 | 201,
163 | 200,
164 | 200
165 | ]
166 | --- response_body eval
167 | [
168 | "",
169 | "hello world",
170 | "",
171 | "Hello, world!",
172 | ""
173 | ]
174 |
175 | === TEST 8: returns 404 for unexisting keys
176 | --- config
177 | location /cb {
178 | set $couchbase_cmd $arg_cmd;
179 | set $couchbase_key $arg_key;
180 | set $couchbase_val $arg_val;
181 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
182 | }
183 | --- request eval
184 | my $key = "test8_" . time();
185 | [
186 | "GET /cb/$key",
187 | "GET /cb/cmd=get&key=$key"
188 | ]
189 | --- error_code eval
190 | [
191 | 404,
192 | 404
193 | ]
194 | --- response_body eval
195 | [
196 | '{"error":"key_enoent","reason":"No such key"}',
197 | '{"error":"key_enoent","reason":"No such key"}'
198 | ]
199 |
200 | === TEST 9: strip location name in the URI key
201 | --- config
202 | location /cb/ {
203 | set $couchbase_cmd $arg_cmd;
204 | set $couchbase_key $arg_key;
205 | set $couchbase_val $arg_val;
206 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
207 | }
208 | --- request eval
209 | my $key = "test9_" . time();
210 | [
211 | "GET /cb/?cmd=set&key=$key&val=value",
212 | "GET /cb/$key"
213 | ]
214 | --- error_code eval
215 | [
216 | 201,
217 | 200
218 | ]
219 | --- response_body eval
220 | [
221 | "",
222 | "value"
223 | ]
224 |
225 | === TEST 10: replace only
226 | --- config
227 | location /cb {
228 | set $couchbase_cmd $arg_cmd;
229 | set $couchbase_key $arg_key;
230 | set $couchbase_val $arg_val;
231 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
232 | }
233 | --- request eval
234 | my $key = "test10_" . time();
235 | "GET /cb?cmd=replace&key=$key&val=blah"
236 | --- error_code: 404
237 | --- response_body eval
238 | '{"error":"key_enoent","reason":"No such key"}'
239 |
240 | === TEST 11: replace after set
241 | --- config
242 | location /cb {
243 | set $couchbase_cmd $arg_cmd;
244 | set $couchbase_key $arg_key;
245 | set $couchbase_val $arg_val;
246 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
247 | }
248 | --- request eval
249 | my $key = "test11_" . time();
250 | [
251 | "GET /cb?cmd=set&key=$key&val=blah",
252 | "GET /cb?cmd=replace&key=$key&val=blah2",
253 | "GET /cb?cmd=get&key=$key",
254 | ]
255 | --- error_code eval
256 | [
257 | 201,
258 | 201,
259 | 200
260 | ]
261 | --- response_body eval
262 | [
263 | "",
264 | "",
265 | "blah2"
266 | ]
267 |
268 | === TEST 12: append
269 | --- config
270 | location /cb {
271 | set $couchbase_cmd $arg_cmd;
272 | set $couchbase_key $arg_key;
273 | set $couchbase_val $arg_val;
274 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
275 | }
276 | --- request eval
277 | my $key = "test12_" . time();
278 | [
279 | "GET /cb?cmd=set&key=$key&val=aaa",
280 | "GET /cb?cmd=append&key=$key&val=bbb",
281 | "GET /cb?cmd=get&key=$key",
282 | ]
283 | --- error_code eval
284 | [
285 | 201,
286 | 201,
287 | 200
288 | ]
289 | --- response_body eval
290 | [
291 | "",
292 | "",
293 | "aaabbb"
294 | ]
295 |
296 | === TEST 13: prepend
297 | --- config
298 | location /cb {
299 | set $couchbase_cmd $arg_cmd;
300 | set $couchbase_key $arg_key;
301 | set $couchbase_val $arg_val;
302 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
303 | }
304 | --- request eval
305 | my $key = "test5_" . time();
306 | [
307 | "GET /cb?cmd=set&key=$key&val=aaa",
308 | "GET /cb?cmd=prepend&key=$key&val=bbb",
309 | "GET /cb?cmd=get&key=$key",
310 | ]
311 | --- error_code eval
312 | [
313 | 201,
314 | 201,
315 | 200
316 | ]
317 | --- response_body eval
318 | [
319 | "",
320 | "",
321 | "bbbaaa"
322 | ]
323 |
324 | === TEST 14: it sets header with CAS
325 | --- skip_nginx
326 | 2: < 1.3.10
327 | --- config
328 | location /cb {
329 | set $couchbase_cmd $arg_cmd;
330 | set $couchbase_key $arg_key;
331 | set $couchbase_val $arg_val;
332 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
333 | add_header X-CAS $couchbase_cas;
334 | }
335 | --- request eval
336 | my $key = "test14_" . time();
337 | "GET /cb?cmd=set&key=$key&val=blah"
338 | --- error_code: 201
339 | --- response_headers_like
340 | X-CAS: \d+
341 |
342 |
343 | === TEST 15: it can be used to cache stuff inside nginx
344 | --- config
345 | location /cb {
346 | internal;
347 | set $couchbase_cmd $arg_cmd;
348 | set $couchbase_key $arg_key;
349 | set $couchbase_val $arg_val;
350 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
351 | }
352 | location /cached {
353 | set $key $uri$args;
354 | srcache_fetch GET /cb key=$key;
355 | srcache_store PUT /cb key=$key;
356 | srcache_store_statuses 200 301 302;
357 | echo "Hello, World!\n";
358 | }
359 | --- request eval
360 | my $key = "test15_" . time();
361 | "GET /cached/$key"
362 | --- error_code: 200
363 |
364 |
365 | === TEST 16: it sets flags
366 | --- config
367 | location /cb {
368 | set $couchbase_cmd $arg_cmd;
369 | set $couchbase_key $arg_key;
370 | set $couchbase_val $arg_val;
371 | set $couchbase_flags $arg_flags;
372 | couchbase_pass $TEST_NGINX_COUCHBASE_HOST;
373 | add_header X-Flags $couchbase_flags;
374 | }
375 | --- request eval
376 | my $key = "test16_" . time();
377 | [
378 | "GET /cb?cmd=set&key=$key&val=blah&flags=3735928559",
379 | "GET /cb?cmd=get&key=$key"
380 | ]
381 | --- error_code eval
382 | [
383 | 201,
384 | 200
385 | ]
386 | --- response_body eval
387 | [
388 | "",
389 | "blah"
390 | ]
391 | --- response_headers_like eval
392 | [
393 | "",
394 | "X-Flags: 3735928559"
395 | ]
396 |
--------------------------------------------------------------------------------