目前為止,我們已經學會了 Moleculer 的主要功能,接著要來介紹相關的模組工具。
首先今天要介紹的是最重要的模組 moleculer-web
,它是針對 Moleculer 框架所開發的官方 API 閘道器服務。你可以透過它來發布 RESTful API 架構風格的服務。
npm install moleculer-web
範例:快速使用
使用預設值啟動 API 閘道器服務之後,你可以在 http://localhost:3000/
存取所有的服務(包含內部 $node
)。
const { ServiceBroker } = require("moleculer");
const ApiService = require("moleculer-web");
const broker = new ServiceBroker();
// 讀取 API 閘道器
broker.createService(ApiService);
// 啟動服務
broker.start();
範例路徑:
test.hello
Action: http://localhost:3000/test/hello
math.add
Action 並夾帶參數: http://localhost:3000/math/add?a=25&b=13
http://localhost:3000/~node/health
http://localhost:3000/~node/actions
如果你不想要將所有的 Actions 都公開,你可以使用白名單選項來過濾它。你也可以使用正規表達式來匹配,要允許所有 Actions 請使用 **
。
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
path: "/api",
whitelist: [
// 訪問 `posts` 服務中所有的 Actions
"posts.*",
// 僅訪問 `users.list` Action
"users.list",
// 訪問 `math` 服務中所有的 Actions
/^math\.\w+$/
]
}]
}
});
你可以使用別名來替代 Action 名稱,然後再指定對應的方法。如果沒有指定方法則會處理所有類型的方法。別名內也可以加入參數,意思就是你可以定義冒號的參數名稱 (:name
)。
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
// 使用 `GET /login` 或 `POST /login` 呼叫 `auth.login` Action
"login": "auth.login",
// 限制請求的方法
"POST users": "users.create",
// 加入 `name` 參數
// 你可以在 Action 中透過 `ctx.params.name` 來取得它
"GET greeter/:name": "test.greeter",
}
}]
}
});
參數名稱是由
path-to-regexp
[2] 套件處理的,所以你也可以使用optional
[3] 與repeated
[4] 參數設定方式。
API 閘道器有實作
listAliases
的 Action ,你可以利用它在 HTTP 端點列出 Actions 映射清單。
範例:你也可以建立 RESTful 風格的 API 。
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
"GET users": "users.list",
"GET users/:id": "users.get",
"POST users": "users.create",
"PUT users/:id": "users.update",
"DELETE users/:id": "users.remove"
}
}]
}
});
範例:如果是 REST 路由風格,你可以使用簡單速記別名。
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
"REST users": "users"
}
}]
}
});
如果使用該速記別名,你需要在服務建立對應的 Action,如:
list
、get
、create
、update
、remove
。
範例:你也可以在別名宣告客製化函數。在這種情況下,需要使用 function (req, res) {...}
。
注意, Moleculer 是使用 Node.js 的 HTTP Server [5]。
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
aliases: {
"POST upload"(req, res) {
this.parseUploadedFile(req, res);
},
"GET custom"(req, res) {
res.end('hello from custom handler')
}
}
}]
}
});
Moleculer 也額外加入了一些內部指標物件可以使用:
req.$ctx
- 指向到請求的 context 。req.$service
與 res.$service
- 指向到服務的實例。可以透過它來訪問 Broker req.$service.broker
。req.$route
與 res.$route
- 指向到解析的路由定義。req.$params
- 指向到解析的參數 (包含 query string 與 post body ) 。req.$alias
- 指向到解析的別名定義。req.$action
- 指向到解析的 Action 。req.$endpoint
- 指向到解析的 Action 端點。req.$next
- 如果請求是來自 Express.js
則指向到 next()
處理。除了指定的路由以外,還有一個 mappingPolicy
屬性規則來處理沒有設定別名的路由。
範例:只能請求 POST /add
,不能請求 /math.add
或 /math/add
all
- 允許所有的路由,無論是否有別名。(預設值)restrict
- 僅允許有設定別名的路由。broker.createService({
mixins: [ApiService],
settings: {
routes: [{
mappingPolicy: "restrict",
aliases: {
"POST add": "math.add"
}
}]
}
});
API 閘道器也支援檔案上傳。你可以使用 multipart form data
(由 busboy 套件支援[6] )或是原始的 body
請求來上傳檔案,但無論如何檔案都會被轉換成串流的 Action。在 multipart form data
模式下,你還可以上傳多個檔案。
範例:
api.service.js
const ApiGateway = require("moleculer-web");
module.exports = {
mixins: [ApiGateway],
settings: {
path: "/upload",
routes: [
{
path: "",
aliases: {
// 由 HTML multipart form 上傳檔案
"POST /": "multipart:file.save",
// 由 AJAX 或 cURL 上傳檔案
"PUT /:id": "stream:file.save",
// HTML form 上傳檔案並覆蓋 busboy 設定
"POST /multi": {
type: "multipart",
// Action 層的 busboy 設定
busboyConfig: {
limits: { files: 3 }
},
action: "file.save"
}
},
// 路由層的 busboy 設定
busboyConfig: {
limits: { files: 1 }
// 可以在這裡設定限制的事件:
// `onPartsLimit` 、 `onFilesLimit` 、 `onFieldsLimit`
},
mappingPolicy: "restrict"
}
]
}
});
Multipart 參數
你可以在 Actions 中使用以下的特定欄位來訪問由 multipart-form
傳遞的檔案參數:
ctx.params
- 終端可讀的檔案串流。ctx.meta.$params
- 由網址得到的 query 參數。ctx.meta.$multipart
- 由 form-data
附加的文字欄位,必須在其它檔案欄位前發送。自動別名可以讓你在服務中宣告你的路由別名,再由閘道器從服務綱目來動態建構完整的路由。
每當服務加入或離開時,閘道器都會重新生成路由。
範例:使用白名單參數來指定服務,讓閘道器能追蹤並建立路由。
api.service.js
module.exports = {
mixins: [ApiGateway],
settings: {
routes: [
{
path: "/api",
whitelist: [
"v2.posts.*",
"test.*"
],
aliases: {
"GET /hi": "test.hello"
},
autoAliases: true
}
]
}
};
posts.service.js
module.exports = {
name: "posts",
version: 2,
settings: {
// 基底服務路徑
// rest: "posts/" // 可以設定需要改變的路徑
// 原始的路徑為 "/api/v2/posts"
// 將會被改為 "/api/posts"
},
actions: {
list: {
// 對外路徑為 "/api/v2/posts/"
rest: "GET /",
handler(ctx) { }
},
get: {
// 對外路徑為 "/api/v2/posts/:id"
rest: "GET /:id",
handler(ctx) { }
},
create: {
rest: "POST /",
handler(ctx) { }
},
update: {
rest: "PUT /:id",
handler(ctx) { }
},
remove: {
rest: "DELETE /:id",
handler(ctx) { }
}
}
};
生成的別名
GET /api/hi => test.hello
GET /api/v2/posts => v2.posts.list
GET /api/v2/posts/:id => v2.posts.get
POST /api/v2/posts => v2.posts.create
PUT /api/v2/posts/:id => v2.posts.update
DELETE /api/v2/posts/:id => v2.posts.remove
範例:設定完整路徑
posts.service.js
module.exports = {
name: "posts",
version: 2,
settings: {
// 基底路徑
rest: "posts/"
},
actions: {
tags: {
// 強制使用 "/tags" 路徑取代原本的 "/api/v2/posts/tags" 路徑
rest: {
method: "GET",
fullPath: "/tags"
},
handler(ctx) {}
}
}
};
API 閘道器會從網址查詢 query
、 params
、 body
並合併它們,然後將結果放在 req.$params
。
你可以透過 mergeParams: false
設定來停用參數合併。在這種情況下,參數將被拆分開。
範例:
broker.createService({
mixins: [ApiService],
settings: {
routes: [{
path: "/",
mergeParams: false
}]
}
});
未被合併的參數
{
// query 參數
query: {
category: "general",
}
// body 參數
body: {
title: "Hello",
content: "...",
createdAt: 1530796920203
},
// params 參數
params: {
id: 5
}
}
相關資訊請參閱 qs[7] 。
範例:陣列參數
// GET /api/opt-test?a=1&a=2
a: ["1", "2"]
範例:物件與陣列
// GET /api/opt-test?foo[bar]=a&foo[bar]=b&foo[baz]=c
foo: {
bar: ["a", "b"],
baz: "c"
}
支援類連結的 Middlewares ,分別為 global-level
、 route-level
及 alias-level
三個層級。你可以建立 function(req, res, next) {...}
函數來使用它。更多請參閱 Express middlewares[8] 。
範例:
broker.createService({
mixins: [ApiService],
settings: {
// 全域層 middlewares ,套用到所有路由
use: [
cookieParser(),
helmet()
],
routes: [
{
path: "/",
// 路由層 middlewares
use: [
compression(),
passport.initialize(),
passport.session(),
serveStatic(path.join(__dirname, "public"))
],
aliases: {
"GET /secret": [
// 別名層 middlewares
auth.isAuthenticated(),
auth.hasRole("admin"),
"top.secret" // 呼叫 `top.secret` action
]
}
}
]
}
});
使用
swagger-stats
[9] 介面快速查看 API 的健康度 (使用 TypeScript) 。
import { Service, ServiceSchema } from "moleculer";
import ApiGatewayService from "moleculer-web";
const swStats = require("swagger-stats");
const swMiddleware = swStats.getMiddleware();
broker.createService({
mixins: [ApiGatewayService],
name: "gw-main",
settings: {
cors: {
methods: ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"],
origin: "*",
},
routes: [
// ...
],
use: [swMiddleware],
},
async started(this: Service): Promise<void> {
this.addRoute({
path: "/",
use: [swMiddleware],
});
},
} as ServiceSchema);
API 閘道器支援在 Middlewares 中使用錯誤處理。如果你拋出錯誤到 next(err)
函數的話,它將會呼叫帶有 err
、 req
、 res
、 next
引數的錯誤處理 Middleware 。
broker.createService({
mixins: [ApiService],
settings: {
use: [
cookieParser(),
helmet()
],
routes: [
{
path: "/",
use: [
compression(),
passport.initialize(),
passport.session(),
function (err, req, res, next) {
this.logger.error("Error is occured in middlewares!");
this.sendError(req, res, err);
}
],
}
]
}
});
它以 serve-static
模組來提供像是 Express.js
的靜態路由服務。
broker.createService({
mixins: [ApiService],
settings: {
assets: {
// assets 靜態檔案的根目錄路徑
folder: "./assets",
// `serve-static` 模組的選項
options: {}
}
}
});
[1] API Gateway, https://moleculer.services/docs/0.14/moleculer-web.html
[2] Path-to-RegExp, https://github.com/pillarjs/path-to-regexp
[3] Path-to-RegExp Optional, https://github.com/pillarjs/path-to-regexp#optional
[4] Path-to-RegExp Zero or more, https://github.com/pillarjs/path-to-regexp#zero-or-more
[5] Node.js HTTP Server, https://nodejs.org/api/http.html
[6] busboy
, https://github.com/mscdex/busboy
[7] qs, https://github.com/ljharb/qs
[8] Express Using middleware, https://expressjs.com/en/guide/using-middleware.html
[9] swagger-stats, https://swaggerstats.io/
[10] serve-static, https://github.com/expressjs/serve-static
services
目錄下一起跑。鐵人賽