File | Date | Author | Commit |
---|---|---|---|
.settings | 2014-03-16 |
![]() |
[201030] for issue #8: enhance of tracing about throwing... |
src | 2014-07-05 |
![]() |
[1de22b] fix bug #30 Nginx worker crashes when to fetch ... |
test | 2014-07-05 |
![]() |
[c64a24] some tests before release 0.2.3 |
.classpath | 2014-02-24 |
![]() |
[0979ff] support external coroutine class waving configu... |
.cproject | 2014-02-24 |
![]() |
[0979ff] support external coroutine class waving configu... |
.gitignore | 2014-05-30 |
![]() |
[e7e72a] in c source file change version 0.2.1 to 0.2.2 ... |
.project | 2014-04-26 |
![]() |
[8bb321] compatible with nginx latest stable 1.6.0 |
HISTORY.md | 2014-07-05 |
![]() |
[4059ba] prepare for release v0.2.3 |
LICENSE | 2014-02-24 |
![]() |
[0979ff] support external coroutine class waving configu... |
README.md | 2014-07-05 |
![]() |
[4059ba] prepare for release v0.2.3 |
logo.png | 2014-01-09 |
![]() |
[6ffea4] add logo |
project.clj | 2014-07-05 |
![]() |
[c64a24] some tests before release 0.2.3 |
Nginx-Clojure is a Nginx module for embedding Clojure or Java programs, typically those Ring based handlers.
There are some core features :
By the way it is very fast, the benchmarks can be found HERE .
The lastest release is 0.2.3. Please check the Update History for more details.
nginx-${os-arc}
to nginx
, eg. for linux is nginx-linux-x64
Nginx-Clojure may be compiled successfully on Linux x64, Win32 and Mac OS X x64.
Setting Java header include path in nginx-clojure/src/c/config
```nginx
JNI_HEADER_1="/usr/lib/jvm/java-7-oracle/include"
JNI_HEADER_2="${JNI_HEADER_1}/linux"
````
1. Add Nginx-Clojure module to Nginx configure command, here is a simplest example without more details about InstallOptions
```bash
$./configure \
--add-module=nginx-clojure/src/c
$ make
$ make install
```
1. Create the jar file about Nginx-Clojure
Please check the lein version lein version
, it should be at least 2.0.0.
bash
$ cd nginx-clojure
$ lein jar
Then you'll find nginx-clojure-${version}.jar (eg. nginx-clojure-0.2.3.jar) in the target folder.
The jar file is self contained and only depend on the clojure core jar, e.g clojure-1.5.1.jar
Setting JVM path and class path within http {
block in nginx.conf
#for win32, jvm_path maybe is "C:/Program Files/Java/jdk1.7.0_25/jre/bin/server/jvm.dll";
#for macosx, jvm_path maybe is "/Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Libraries/libserver.dylib";
#for ubuntu, jvm_path maybe is "/usr/lib/jvm/java-7-oracle/jre/lib/amd64/server/libjvm.so";
#for centos, jvm_path maybe is "/usr/java/jdk1.6.0_45/jre/lib/amd64/server/libjvm.so";
#for centos 32bit, jvm_path maybe is "/usr/java/jdk1.7.0_51/jre/lib/i386/server/libjvm.so";
jvm_path "/usr/lib/jvm/java-7-oracle/jre/lib/amd64/server/libjvm.so";
#jvm_options can be repeated once per option.
#for win32, class path seperator is ";", jvm_options maybe "-Djava.class.path=jars/nginx-clojure-0.2.3.jar;jars/clojure-1.5.1.jar";
jvm_options "-Djava.class.path=jars/nginx-clojure-0.2.3.jar:jars/clojure-1.5.1.jar";
#jvm heap memory
jvm_options "-Xms1024m";
jvm_options "-Xmx1024m";
#for enable java remote debug uncomment next two lines, make sure "master_process = off;" or "worker_processes = 1;"
#jvm_options "-Xdebug";
#jvm_options "-Xrunjdwp:server=y,transport=dt_socket,address=8400,suspend=n";
````
###Some Useful Tips
These tips are really useful. Most of them are from real users. Thanks [Rickr Nook](https://github.com/rickr-nook) who give us some useful tips.
1. When importing Swing We Must specifiy `jvm_options "-Djava.awt.headless=true"` , otherwise the nginx will hang.
1. By adding the location of your clojure source files to the classpath,then just issue "nginx -s reload" and changes to the sources get picked up!
1. You can remove clojure-1.5.1.jar from class path and point at your "lein uberjar" to pick up a different version of clojure.
1. To use Java 7 on OSX, in nginx.conf your may set `jvm_path "/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home/jre/lib/server/libjvm.dylib";`
2.2 Initialization Handler for nginx worker
-----------------
You can embed clojure code in the `http { ` block to do initialization when nginx worker starting. e.g
```clojure
http {
......
clojure;
clojure_code '
(fn[ctx]
(.println System/err "on http clojure context")
)
';
....
}
Now ctx
is only empty map but in the future we will give more information with it.
clojure code should return a ring handler which can use status 500 and body to report some errors or just return nothing.
Please Keep these in your mind:
If you enabled coroutine support, nginx maybe will start successfully even if your initialization failed after some socket operations. If you case it, you can
use nginx.clojure.core/without-coroutine
to wrap your handler, e.g.
nginx
clojure_code '
(do
(use \'nginx.clojure.core)
(without-coroutine
(fn[ctx]
....
)
))
';
Within location
block, directive clojure
is an enable flag and directive clojure_code
is used to setting a Ring handler.
location /clojure {
clojure;
clojure_code '
(fn[req]
{
:status 200,
:headers {"content-type" "text/plain"},
:body "Hello Clojure & Nginx!"
})
';
}
Now you can start nginx and access http://localhost:8080/clojure, if some error happens please check error.log file.
(ns my.hello)
(defn hello-world [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
You should set your clojure JAR files to class path, see 2.1 JVM Path , Class Path & Other JVM Options .
location /myClojure {
clojure;
clojure_code '
(do
(use \'[my.hello])
hello-world))
';
}
For more details and more useful examples for Compojure which is a small routing library for Ring that allows web applications to be composed of small, independent parts. Please refer to https://github.com/weavejester/compojure
package my;
import nginx.clojure.Constants;
import clojure.lang.AFn;
import clojure.lang.IPersistentMap;
import clojure.lang.PersistentArrayMap;
public class HelloHandler extends AFn {
@Override
public Object invoke(Object r) {
IPersistentMap req = (IPersistentMap)r;
//get some info from req. eg. req.valAt(Constants.QUERY_STRING)
//....
//prepare resps, more details about Ring handler on this site https://github.com/ring-clojure/ring/blob/master/SPEC
Object[] resps = new Object[] {Constants.STATUS, 200,
Constants.HEADERS, new PersistentArrayMap(new Object[]{Constants.CONTENT_TYPE.getName(),"text/plain"}),
Constants.BODY, "Hello Java & Nginx!"};
return new PersistentArrayMap(resps);
}
}
In nginx.conf, eg.
location /java {
clojure;
clojure_code '
(do (import \'[my HelloHandler]) (HelloHandler.) )
';
}
You should set your JAR files to class path, see 2.1 JVM Path , Class Path & Other JVM Options .
If the http service should do some slow I/O operations such as access external http service, database, etc. nginx worker will be blocked by those operations
and the new user request even static file request will be blocked. It really sucks! Before v0.2.0 the only choice is using thread pool but now we have
three choice :
Turn on Run Tool Mode
```nginx
http {
...
worker_processes 1;
jvm_options "-javaagent:jars/nginx-clojure-0.2.3.jar=tmb";
jvm_options "-Xbootclasspath/a:jars/nginx-clojure-0.2.3.jar:jars/clojure-1.5.1.jar";
...
}
```
* Setting Output Path of Waving Configuration File
```nginx
jvm_options "-Dnginx.clojure.wave.CfgToolOutFile=/tmp/my-wave-cfg.txt";
```
* Setting Dump Configuration Service
```nginx
location /dump {
clojure;
clojure_code '
(do (import \'[nginx.clojure.wave SuspendMethodTracer])
(fn[req]
(SuspendMethodTracer/dump)
{:status 200, :body "ok", :headers {"content-type" "text/plain"}}
))
';
}
``
* Start Nginx which Compiled with Nginx Clojure Module
* Run curl or httpclient based junit tests to access your http services which directly or indirectly use Java Socket API, e.g Apache Http Client, MySQL JDBC Driver etc.
* After All responses completed We'll get a generated class waving configuration file e.g
my-wave-cfg.txt` by access Dump Configuration Service.
bash
/*use curl or just put the Dump Service url to browser and click GO!
*Dump Service will generate Waving Configuration File to the path defined by
*java system property `nginx.clojure.wave.CfgToolOutFile`
*/
curl -v http://localhost:8080/dump
Don't foget reset worker_processes
and turn off run tool mode for product enviroument after get class waving configuration
Turn on Coroutine Support
```nginx
http {
...
worker_processes 8;
jvm_options "-javaagent:jars/nginx-clojure-0.2.3.jar=mb";
jvm_options "-Xbootclasspath/a:jars/nginx-clojure-0.2.3.jar:jars/clojure-1.5.1.jar";
jvm_options "-Djava.class.path=coroutine-udfs:YOUR_CLASSPATH_HERE";
jvm_options "-Dnginx.clojure.wave.udfs=my-wave-cfg.txt";
...
}
```
restart nginx or reload nginx
Now every nginx worker can handle thousands of connections easily!
Those plain and old java socket based code such as Apache Http Client, MySQL mysql jdbc drivers etc. will be on the fly with epoll/kqueue on Linux/BSD!
Nginx won't blocked until nginx connections exhuasted or jvm OutOfMemory!
Asynchronous Socket Can be used with default mode or coroutined enabled mode without any additional settings. It just a set of API.
It uses event driven pattern and works with a java callback handler or clojure function for callback.
Please check the source code and examples for more details.
In future we'll give more clojure style wrapper and examples. Pull requests are also welcome!
If your tasks are often blocked by slow I/O operations, the thread pool method can make the nginx worker not blocked until
all threads are exhuasted. When facing large amount of connections this choice isn't as good as above coroutine based choice or asynchronous socket.
eg.
#turn off coroutine mode, n means do nothing. You can also comment this line to turn off coroutine mode
jvm_options "-javaagent:jars/nginx-clojure-0.2.3.jar=nmb";
jvm_workers 40;
Now Nginx-Clojure will create a thread pool with fixed 40 threads per JVM instance/Nginx worker to handle requests. If you get more memory, you can set
a bigger number.
Here's a simple example for Nginx rewrite handler :
set $myvar "";
location /rewritesimple {
clojure;
clojure_rewrite_code '
(do (use \'[nginx.clojure.core])
(fn[req]
(set-ngx-var! req "myvar" "Hello")
phrase-done))
';
clojure_code '
(do (use \'[nginx.clojure.core])
(fn[req]
(set-ngx-var! req "myvar"
(str (get-ngx-var req "myvar") "," "Xfeep!"))
{
:status 200,
:headers {"content-type" "text/plain"},
:body (get-ngx-var req "myvar")
}))
';
}
We can alos use this feature to complete a simple dynamic balancer , e.g.
set $myhost "";
location /myproxy {
clojure;
clojure_rewrite_code '
(do (use \'[nginx.clojure.core])
(fn[req]
;compute myhost (upstream name or real host name) based req & remote service, e.g.
(let [myhost (compute-myhost req)])
(set-ngx-var! req "myhost" myhost)
phrase-done))
';
proxy_pass $myhost
}
The equivalent java code is here
package my.test;
import clojure.lang.AFn;
import clojure.lang.IPersistentMap;
public static class MyRewriteProxyPassHandler extends AFn {
@Override
public Object invoke(Object arg) {
LazyRequestMap req = (LazyRequestMap)arg;
String myhost = computeMyHost(req);
NginxClojureRT.setNGXVariable(req.nativeRequest(), "myhost", myhost);
return NginxClojureRT.PHRASE_DONE;
}
private String computeMyHost(LazyRequestMap req) {
//compute a upstream name or host name;
}
}
Then we set the java rewrtite handler in nginx.conf
set $myhost "";
location /myproxy {
clojure;
clojure_rewrite_code '
(do (import \'[my.test MyRewriteProxyPassHandler])
(MyRewriteProxyPassHandler.))
';
proxy_pass $myhost
}
For clojure
clojure;
clojure_rewrite_code '
(do (use \'[nginx.clojure.core])
(import \'[com\.test AuthenticationHandler])
(fn[req]
(if ((AuthenticationHandler.) req)
;AuthenticationHandler returns true so we go to proxy_pass
phrase-done
;else return 403
{:status 403}
)))
';
proxy_pass http://localhost:8084;
For Java
* nginx.conf
```nginx
clojure;
clojure_rewrite_code '
(do
(import \'[com.test MyHandler] (MyHandler.)))';
proxy_pass http://localhost:8084;
```
MyHandler.java
```java
public Object invoke(Object req) {
/*do some computing here*/
if (goto-proxy-pass) {
return NginxClojureRT/PHRASE_DONE;
}else { //return 403
Object[] resps = new Object[] {
Constants.STATUS, 403,
//add some headers -- optional for no-20X response
//Constants.HEADERS, new PersistentArrayMap(new Object[]{Constants.CONTENT_TYPE.getName(),"text/plain"}),
//body text -- optional for no-20X response
// Constants.BODY, "xxxxxxxxxxxxxx!"
};
return new PersistentArrayMap(resps);
}
}
```
Sometimes we need invoke serveral remote services before completing the ring response. For better performance we need a way to handle multiple sockets parallel in sub coroutines.
e.g. fetch two page parallel by clj-http
(let [[r1, r2]
(co-pvalues (client/get "http://service1-url")
(client/get "http://service2-url"))]
;println bodies of two remote response
(println (str (:body r1) "====\n" (:body r2) ))
Here co-pvalues
is also non-blocking and coroutine based. In fact it will create two sub coroutines to handle two sockets.
Generally use redis or memorycached is the better choice to implement a shared map among Nginx workers. We can do some initialization of the
shared map by following the guide of 2.2 Initialization Handler for nginx worker.
If you like shared map managed in nginx process better than redis or memcached, you can choose SharedHashMap
which is fast and based on Memory Mapped File so that it can store large amout of records and won't need too much java heap memory.
Some web services need user defined http request method to define special operations beyond standard http request methods.
e.g. We use MYUPLOAD
to upload a file and overwrite the one if it exists. The curl
command maybe is
curl -v -X MYUPLOAD --upload-file post-test-data \
"http://localhost:8080/myservice"
In the nginx.conf, we can use always_read_body on;
to force nginx to read http body.
location /myservice {
clojure;
always_read_body on;
clojure_code '....';
}
Copyright © 2013-2014 Zhang, Yuexiang (xfeep) and released under the BSD 3-Clause license.
This program uses:
* Re-rooted ASM bytecode engineering library which is distributed under the BSD 3-Clause license
* Modified Continuations Library Written by Matthias Mann is distributed under the BSD 3-Clause license