App Samples§
Note
These steps assume Unit was already installed with the language module for each app.
Go§
Let’s configure the following basic app, saved as /www/app.go:
package main
import (
"io"
"net/http"
"unit.nginx.org/go"
)
func main() {
http.HandleFunc("/",func (w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, Go on Unit!")
})
unit.ListenAndServe(":8080", nil)
}
First, create a Go module, go
get
Unit’s package, and build your application:
$ go mod init example.com/app
$ go get unit.nginx.org/go@1.33.0
$ go build -o /www/app /www/app.go
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/go"
}
},
"applications": {
"go": {
"type": "external",
"working_directory": "/www/",
"executable": "/www/app"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, Go on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example:
package main
import (
"crypto/sha256";
"fmt";
"io";
"io/ioutil";
"encoding/json";
"net/http";
"strings";
"unit.nginx.org/go"
)
func formatRequest(r *http.Request) string {
h := make(map[string]string)
m := make(map[string]string)
t := make(map[string]interface{})
m["message"] = "Unit reporting"
m["agent"] = "NGINX Unit 1.33.0"
body, _ := ioutil.ReadAll(r.Body)
m["body"] = fmt.Sprintf("%s", body)
m["sha256"] = fmt.Sprintf("%x", sha256.Sum256([]byte(m["body"])))
data, _ := json.Marshal(m)
for name, _ := range r.Header {
h[strings.ToUpper(name)] = r.Header.Get(name)
}
_ = json.Unmarshal(data, &t)
t["headers"] = h
js, _ := json.MarshalIndent(t, "", " ")
return fmt.Sprintf("%s", js)
}
func main() {
http.HandleFunc("/",func (w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
io.WriteString(w, formatRequest(r))
})
unit.ListenAndServe(":8080", nil)
}
Java§
Let’s configure the following basic app, saved as /www/index.jsp:
<%@ page language="java" contentType="text/plain" %>
<%= "Hello, JSP on Unit!" %>
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/java"
}
},
"applications": {
"java": {
"type": "java",
"webapp": "/www/"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, JSP on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example (you’ll
need to download and add the json-simple library to your app’s
classpath option):
<%@ page language="java" contentType="application/json; charset=utf-8" %>
<%@ page import="com.github.cliftonlabs.json_simple.JsonObject" %>
<%@ page import="com.github.cliftonlabs.json_simple.Jsoner" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.math.BigInteger" %>
<%@ page import="java.nio.charset.StandardCharsets" %>
<%@ page import="java.security.MessageDigest" %>
<%@ page import="java.util.Enumeration" %>
<%
JsonObject r = new JsonObject();
r.put("message", "Unit reporting");
r.put("agent", "NGINX Unit 1.33.0");
JsonObject headers = new JsonObject();
Enumeration h = request.getHeaderNames();
while (h.hasMoreElements()) {
String name = (String)h.nextElement();
headers.put(name, request.getHeader(name));
}
r.put("headers", headers);
BufferedReader br = request.getReader();
String body = "";
String line = br.readLine();
while (line != null) {
body += line;
line = br.readLine();
}
r.put("body", body);
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] bytes = md.digest(body.getBytes(StandardCharsets.UTF_8));
BigInteger number = new BigInteger(1, bytes);
StringBuilder hex = new StringBuilder(number.toString(16));
r.put("sha256", hex.toString());
out.println(Jsoner.prettyPrint((Jsoner.serialize(r))));
%>
Node.js§
Let’s configure the following basic app, saved as /www/app.js:
#!/usr/bin/env node
require("unit-http").createServer(function (req, res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("Hello, Node.js on Unit!")
}).listen()
Make it executable and link the Node.js language package you’ve installed earlier:
$ cd /www
$ chmod +x app.js
$ npm link unit-http
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/node"
}
},
"applications": {
"node": {
"type": "external",
"working_directory": "/www/",
"executable": "app.js"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, Node.js on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example:
#!/usr/bin/env node
const cr = require("crypto")
const bd = require("body")
require("unit-http").createServer(function (req, res) {
bd (req, res, function (err, body) {
res.writeHead(200, {"Content-Type": "application/json; charset=utf-8"})
var r = {
"agent": "NGINX Unit 1.33.0",
"message": "Unit reporting"
}
r["headers"] = req.headers
r["body"] = body
r["sha256"] = cr.createHash("sha256").update(r["body"]).digest("hex")
res.end(JSON.stringify(r, null, " ").toString("utf8"))
})
}).listen()
Note
You can run a version of the same app without requiring the unit-http module explicitly.
Perl§
Let’s configure the following basic app, saved as /www/app.psgi:
my $app = sub {
return [
"200",
[ "Content-Type" => "text/plain" ],
[ "Hello, Perl on Unit!" ],
];
};
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/perl"
}
},
"applications": {
"perl": {
"type": "perl",
"working_directory": "/www/",
"script": "/www/app.psgi"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, Perl on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example:
use strict;
use Digest::SHA qw(sha256_hex);
use JSON;
use Plack;
use Plack::Request;
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
$res->header("Content-Type" => "application/json; charset=utf-8");
my $r = {
"message" => "Unit reporting",
"agent" => "NGINX Unit 1.33.0",
"headers" => $req->headers->psgi_flatten(),
"body" => $req->content,
"sha256" => sha256_hex($req->content),
};
my $json = JSON->new();
$res->body($json->utf8->pretty->encode($r));
return $res->finalize();
};
PHP§
Let’s configure the following basic app, saved as /www/index.php:
<?php echo "Hello, PHP on Unit!"; ?>
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/php"
}
},
"applications": {
"php": {
"type": "php",
"root": "/www/"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, PHP on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example:
<?php
header("Content-Type: application/json; charset=utf-8");
$r = array (
"message" => "Unit reporting",
"agent" => "NGINX Unit 1.33.0"
);
foreach ($_SERVER as $header => $value)
if (strpos($header, "HTTP_") === 0)
$r["headers"][$header] = $value;
$r["body"] = file_get_contents("php://input");
$r["sha256"] = hash("sha256", $r["body"]);
echo json_encode($r, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
?>
Python§
Let’s configure the following basic app, saved as /www/wsgi.py:
def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return (b"Hello, Python on Unit!")
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/python"
}
},
"applications": {
"python": {
"type": "python",
"path": "/www/",
"module": "wsgi"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, Python on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example:
import hashlib, json
def application(env, start_response):
start_response("200 OK", [("Content-Type",
"application/json; charset=utf-8")])
r = {}
r["message"] = "Unit reporting"
r["agent"] = "NGINX Unit 1.33.0"
r["headers"] = {}
for header in [_ for _ in env.keys() if _.startswith("HTTP_")]:
r["headers"][header] = env[header]
bytes = env["wsgi.input"].read()
r["body"] = bytes.decode("utf-8")
r["sha256"] = hashlib.sha256(bytes).hexdigest()
return json.dumps(r, indent=4).encode("utf-8")
Ruby§
Let’s configure the following basic app, saved as /www/config.ru:
app = Proc.new do |env|
["200", {
"Content-Type" => "text/plain",
}, ["Hello, Ruby on Unit!"]]
end
run app
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"*:8080": {
"pass": "applications/ruby"
}
},
"applications": {
"ruby": {
"type": "ruby",
"working_directory": "/www/",
"script": "config.ru"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
Hello, Ruby on Unit!
Try this sample out with the Dockerfile here
or use a more elaborate app example:
require "digest"
require "json"
app = Proc.new do |env|
body = env["rack.input"].read
r = {
"message" => "Unit reporting",
"agent" => "NGINX Unit 1.33.0",
"body" => body,
"headers" => env.select { |key, value| key.include?("HTTP_") },
"sha256" => Digest::SHA256.hexdigest(body)
}
["200", {
"Content-Type" => "application/json; charset=utf-8",
}, [JSON.pretty_generate(r)]]
end;
run app
WebAssembly (Wasm)§
Instead of dealing with bytecode, let’s build a Unit-capable Rust app and compile it into a WebAssembly (Wasm) component.
Make sure you have the Rust toolchain (cargo, rustc, etc.) installed. We recommend using rustup to get started.
This example was built with rustc version 1.76.0.
Start by adding the wasm32-wasi support as a compilation target for rustc
$ rustup target add wasm32-wasi
Next, install cargo component. This simplifies building a WebAssembly component from Rust Code, making it the recommended method.
$ cargo install cargo-component
Currently, the fastest way to get started with WebAssembly components using WASI 0.2 wasi-http API is the hello-wasi-http demo application by Dan Gohman. Clone the repository and build the component running the following command:
$ git clone https://github.com/sunfishcode/hello-wasi-http
$ cd hello-wasi-http
$ cargo component build
The output of the build command should be similar to this:
$ cargo component build
Compiling hello-wasi-http v0.0.0 (/home/unit-build/hello-wasi-http)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Creating component /home/unit-build/hello-wasi-http/target/wasm32-wasi/debug/hello_wasi_http.wasm
$
This creates a WebAssembly component you can deploy on Unit using the following Unit configuration. Make sure you point the component path to the WebAssembly component you have just created. Create a config.json file:
{
"listeners": {
"127.0.0.1:8080": {
"pass": "applications/wasm"
}
},
"applications": {
"wasm": {
"type": "wasm-wasi-component",
"component": "/home/unit-build/hello-wasi-http/target/wasm32-wasi/debug/hello_wasi_http.wasm"
}
}
}
Apply the Unit configuration by using the CLI:
$ unitc /config < config.json
Or by sending it manually to Units control API:
$ cat config.json | curl -X PUT -d @- --unix-socket /path/to/control.unit.sock http://localhost/config/
Congratulations! You have created your very first WebAssembly component on Unit! Send a GET Request to your configured listener.
$ curl http://localhost:8080
Warning
Unit 1.32.0 and later support the WebAssembly component Model and WASI 0.2 APIs. We recommend to use the new implementation.
Instead of dealing with bytecode, let’s build a Unit-capable Rust app and compile it into WebAssembly.
Note
Currently, WebAssembly support is provided as a Technology Preview. This includes support for compiling Rust and C code into Unit-compatible WebAssembly, using our SDK in the form of the the libunit-wasm library. For details, see our unit-wasm repository on GitHub.
First, install the WebAssembly-specific Rust tooling:
$ rustup target add wasm32-wasi
Next, initialize a new Rust project with a library target (apps are loaded by Unit’s WebAssembly module as dynamic libraries). Then, add our unit-wasm crate to enable the libunit-wasm library:
$ cargo init --lib wasm_on_unit
$ cd wasm_on_unit/
$ cargo add unit-wasm
Append the following to Cargo.toml:
[lib]
crate-type = ["cdylib"]
Save some sample code from our unit-wasm repo as src/lib.rs:
wget -O src/lib.rs https://raw.githubusercontent.com/nginx/unit-wasm/main/examples/rust/echo-request/src/lib.rs
Build the Rust module with WebAssembly as the target:
$ cargo build --target wasm32-wasi
This yields the target/wasm32-wasi/debug/wasm_on_unit.wasm file (path may depend on other options).
Upload the app config to Unit and test it:
# curl -X PUT --data-binary '{
"listeners": {
"127.0.0.1:8080": {
"pass": "applications/wasm"
}
},
"applications": {
"wasm": {
"type": "wasm",
"module": "/path/to/wasm_on_unit/target/wasm32-wasi/debug/wasm_on_unit.wasm",
"request_handler": "uwr_request_handler",
"malloc_handler": "luw_malloc_handler",
"free_handler": "luw_free_handler",
"module_init_handler": "uwr_module_init_handler",
"module_end_handler": "uwr_module_end_handler"
}
}
}' --unix-socket /path/to/control.unit.sock http://localhost/config/
$ curl http://localhost:8080
* Welcome to WebAssembly in Rust on Unit! [libunit-wasm (0.3.0/0x00030000)] *
[Request Info]
REQUEST_PATH = /
METHOD = GET
VERSION = HTTP/1.1
QUERY =
REMOTE = 127.0.0.1
LOCAL_ADDR = 127.0.0.1
LOCAL_PORT = 8080
SERVER_NAME = localhost
[Request Headers]
Host = localhost:8080
User-Agent = curl/8.2.1
Accept = */*
Further, you can research the Unit-based WebAssembly app internals in more depth. Clone the unit-wasm repository and build the examples in C and Rust (may require clang and lld):
$ git clone https://github.com/nginx/unit-wasm/
$ cd unit-wasm
$ make help # Explore your options first
$ make WASI_SYSROOT=/path/to/wasi-sysroot/ examples # C examples
$ make WASI_SYSROOT=/path/to/wasi-sysroot/ examples-rust # Rust examples
Note
If the above commands fail like this:
wasm-ld: error: cannot open .../lib/wasi/libclang_rt.builtins-wasm32.a: No such file or directory
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Download and install the library to clang’s run-time dependency directory:
$ wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/libclang_rt.builtins-wasm32-wasi-20.0.tar.gz \
| tar zxf - # Unpacks to lib/wasi/ in the current directory
$ clang -print-runtime-dir # Double-check the run-time directory, which is OS-dependent
/path/to/runtime/dir/linux
# mkdir /path/to/runtime/dir/wasi # Note the last part of the pathname
# cp lib/wasi/libclang_rt.builtins-wasm32.a /path/to/runtime/dir/wasi/