NGINX Unit

应用示例§

注意

这些步骤假设已使用每个应用的语言模块安装Unit。

Go§

让我们配置以下基本应用,保存为 /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)
}

首先,创建一个Go 模块go getUnit 的软件包,并构建你的应用程序

$ go mod init example.com/app
$ go get unit.nginx.org/go@1.32.1
$ go build -o /www/app /www/app.go

应用配置上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处试用此示例,或使用更精细的应用示例

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.32.1"

    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§

让我们配置以下基本应用,保存为 /www/index.jsp

<%@ page language="java" contentType="text/plain" %>
<%= "Hello, JSP on Unit!" %>

应用配置上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处 尝试此示例,或使用更精细的应用程序示例(您需要下载添加 json-simple 库到应用程序的类路径选项)

<%@ 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.32.1");

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§

让我们配置以下基本应用程序,将其保存为/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()

使其可执行,并链接您之前安装的 Node.js 语言包

$ cd /www
$ chmod +x app.js
$ npm link unit-http

应用程序配置上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处 尝试此示例,或使用更精细的应用程序示例

#!/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.32.1",
            "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()

注意

您可以运行同一应用程序的一个版本,无需显式要求unit-http 模块。

Perl§

让我们配置以下基本应用程序,将其保存为/www/app.psgi

my $app = sub {
    return [
        "200",
        [ "Content-Type" => "text/plain" ],
        [ "Hello, Perl on Unit!" ],
    ];
};

应用程序配置上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处 尝试此示例,或使用更精细的应用程序示例

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.32.1",
        "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§

让我们配置以下基本应用程序,将其保存为/www/index.php

<?php echo "Hello, PHP on Unit!"; ?>

应用程序配置上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处 尝试此示例,或使用更精细的应用程序示例

<?php

header("Content-Type: application/json; charset=utf-8");

$r = array (
   "message" => "Unit reporting",
   "agent"   => "NGINX Unit 1.32.1"
);

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§

让我们配置以下基本应用程序,将其另存为 /www/wsgi.py

def application(environ, start_response):
    start_response("200 OK", [("Content-Type", "text/plain")])
    return (b"Hello, Python on Unit!")

应用程序配置 上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处 试用此示例,或使用更精细的应用程序示例

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.32.1"

    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§

让我们配置以下基本应用程序,将其另存为 /www/config.ru

app = Proc.new do |env|
    ["200", {
        "Content-Type" => "text/plain",
    }, ["Hello, Ruby on Unit!"]]
end

run app

应用程序配置 上传到 Unit 并对其进行测试

# 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!

使用 Dockerfile 此处 试用此示例,或使用更精细的应用程序示例

require "digest"
require "json"

app = Proc.new do |env|
    body = env["rack.input"].read
    r = {
        "message" => "Unit reporting",
        "agent"   => "NGINX Unit 1.32.1",
        "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)§

我们不必处理字节码,而是构建一个支持 Unit 的 Rust 应用程序,并将其编译成 WebAssembly (Wasm) 组件。

确保已安装 Rust 工具链(cargo、rustc 等)。我们建议使用 rustup 入门。

此示例使用 rustc 版本 1.76.0 构建。

首先,将 wasm32-wasi 支持作为 rustc 的编译目标添加

$ rustup target add wasm32-wasi

接下来,安装 cargo 组件。这简化了从 Rust 代码构建 WebAssembly 组件的过程,使其成为推荐的方法。

$ cargo install cargo-component

目前,使用 WASI 0.2 wasi-http API 开始使用 WebAssembly 组件的最快方法是 Dan Gohman 的 hello-wasi-http 演示应用程序。克隆存储库并运行以下命令构建组件

$ git clone https://github.com/sunfishcode/hello-wasi-http
$ cd hello-wasi-http
$ cargo component build

构建命令的输出应类似于此

$ 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
$

这将创建一个 WebAssembly 组件,你可以使用以下 Unit 配置将其部署在 Unit 上。确保将 component 路径指向你刚刚创建的 WebAssembly 组件。创建一个 config.json 文件

{
   "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"
      }
   }
}

使用 CLI 应用 Unit 配置

$ unitc /config < config.json

或将其手动发送到 Units 控制 API

$ cat config.json | curl -X PUT -d @- --unix-socket /path/to/control.unit.sock http://localhost/config/

恭喜!你已在 Unit 上创建了第一个 WebAssembly 组件!向你配置的侦听器发送 GET 请求。

$ curl http://localhost:8080

警告

Unit 1.32.0 及更高版本支持 WebAssembly 组件模型和 WASI 0.2 API。我们建议使用新实现。

我们不处理字节码,而是构建一个支持 Unit 的 Rust 应用,并将其编译成 WebAssembly。

注意

目前,WebAssembly 支持作为技术预览提供。这包括使用我们的 SDK 以 libunit-wasm 库的形式,将 Rust 和 C 代码编译成兼容 Unit 的 WebAssembly。有关详细信息,请参阅我们在 GitHub 上的 unit-wasm 存储库

首先,安装特定于 WebAssembly 的 Rust 工具

$ rustup target add wasm32-wasi

接下来,使用库目标初始化一个新的 Rust 项目(应用由 Unit 的 WebAssembly 模块加载为动态库)。然后,添加我们的 unit-wasm 箱子以启用 libunit-wasm

$ cargo init --lib wasm_on_unit
$ cd wasm_on_unit/
$ cargo add unit-wasm

将以下内容追加到 Cargo.toml

[lib]
crate-type = ["cdylib"]

将我们 unit-wasm 存储库中的部分示例代码保存为 src/lib.rs

wget -O src/lib.rs https://raw.githubusercontent.com/nginx/unit-wasm/main/examples/rust/echo-request/src/lib.rs

使用 WebAssembly 作为目标构建 Rust 模块

$ cargo build --target wasm32-wasi

这会生成 target/wasm32-wasi/debug/wasm_on_unit.wasm 文件(路径可能取决于其他选项)。

应用配置 上传到 Unit 并对其进行测试

# 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 = */*

此外,你可以更深入地研究基于 Unit 的 WebAssembly 应用内部结构。克隆 unit-wasm 存储库并构建 C 和 Rust 中的示例(可能需要 clanglld

$ 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

注意

如果上述命令失败,如下所示

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)

下载并安装库到 clang 的运行时依赖项目录

$ 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/