Egg Learning Notes (3) - Middleware
In the previous chapters, we introduced that Egg is implemented based on Koa, so the middleware form in Egg is the same as in Koa, both based on the onion ring model. Every time we write a middleware, it is equivalent to adding an outer layer to the onion.
Original document address: Middleware
Writing Middleware
Syntax
Let's first take a look at how to write middleware by creating a simple gzip middleware.
// app/middleware/gzip.js
const isJSON = require("koa-is-json");
const zlib = require("zlib");
async function gzip(ctx, next) {
await next();
// Convert the response body to gzip after the subsequent middleware has finished execution
let body = ctx.body;
if (!body) return;
if (isJSON(body)) body = JSON.stringify(body);
// Set gzip body, fix the response header
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set("Content-Encoding", "gzip");
}As you can see, the syntax of the framework's middleware is exactly the same as Koa's middleware, so any Koa middleware can be directly used by the framework.
Configuration
Generally, middleware also has its own configuration. In the framework, a complete middleware includes configuration processing. By convention, a middleware is a standalone file placed in the app/middleware directory, and it needs to export a regular function that accepts two parameters:
options: The configuration options for the middleware. The framework will pass inapp.config[${middlewareName}].app: TheApplicationinstance of the current application.
We will do a simple optimization for the gzip middleware above, allowing it to support gzip compression only when the body size is larger than a configured threshold. We need to create a new file gzip.js in the app/middleware directory.
const isJSON = require("koa-is-json");
const zlib = require("zlib");
module.exports = (options) => {
return async function gzip(ctx, next) {
await next();
// Convert the response body to gzip after the subsequent middleware has finished execution
let body = ctx.body;
if (!body) return;
// Support options.threshold
if (options.threshold && ctx.length < options.threshold) return;
if (isJSON(body)) body = JSON.stringify(body);
// Set gzip body, fix the response header
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set("Content-Encoding", "gzip");
};
};Using Middleware
After writing the middleware, we still need to manually mount it. The following methods are supported:
Using Middleware in the Application
In the application, we can entirely rely on configuration to load custom middleware and determine their execution order.
If we need to load the gzip middleware mentioned above, simply add the following configuration to config.default.js to enable and configure the middleware:
module.exports = {
// Configure the required middleware. The array order represents the loading order
middleware: ["gzip"],
// Configure the gzip middleware
gzip: {
threshold: 1024, // Response bodies smaller than 1k will not be compressed
},
};This configuration will eventually be merged into app.config.appMiddleware at startup.
Using Middleware in the Framework and Plugins
The framework and plugins do not support matching middleware in config.default.js. The following method should be used instead:
// app.js
module.exports = (app) => {
// Log request time at the very front of the middleware stack
app.config.coreMiddleware.unshift("report");
};
// app/middleware/report.js
module.exports = () => {
return async function (ctx, next) {
const startTime = Date.now();
await next();
// Report request time
reportTime(Date.now() - startTime);
};
};Middleware defined at the application layer (app.config.appMiddleware) and default framework middleware (app.config.coreMiddleware) will both be loaded by the loader and mounted on app.middleware.
Using Middleware in Routers
The middleware configured via the above two methods are global and will process every request. If you only want it to apply to a single route, you can instantiate and mount it directly in app/router.js, as shown below:
module.exports = (app) => {
const gzip = app.middleware.gzip({ threshold: 1024 });
app.router.get("/needgzip", gzip, app.controller.handler);
};Default Framework Middleware
In addition to the middleware loaded by the application layer, the framework itself and other plugins will also load many middlewares. All configurations for these built-in middlewares can be modified by updating the corresponding middleware configuration keys. For example, the framework comes with a bodyParser middleware (the framework's loader will modify various separators in the file name into camelCase variable names). If we want to modify the configuration of bodyParser, we only need to write the following in config/config.default.js:
module.exports = {
bodyParser: {
jsonLimit: "10mb",
},
};Note: Middlewares loaded by the framework and plugins will run before those configured at the application layer. Default framework middlewares cannot be overwritten by application-layer middlewares. If the application layer has a custom middleware with the same name, an error will be thrown during startup.
General Configurations
Whether it's middleware loaded by the application layer or built-in framework middleware, both support a few general configuration options:
enable: Controls whether the middleware is enabled.match: Sets conditions so that only requests matching certain rules will pass through this middleware.ignore: Sets conditions so that requests matching certain rules will skip this middleware.
enable
If our application does not need the default bodyParser middleware for request body parsing, we can disable it by setting enable to false:
module.exports = {
bodyParser: {
enable: false,
},
};match and ignore
match and ignore support the same parameters, but their effects are exactly opposite. match and ignore cannot be configured simultaneously.
If we want gzip to be enabled only for URL requests starting with the /static prefix, we can configure the match option:
module.exports = {
gzip: {
match: "/static",
},
};match and ignore support various types of configuration methods:
- String: When the parameter is a string, it configures a URL path prefix. All URLs that have the configured string as a prefix will be matched. Of course, you can also use an array of strings directly.
- Regex: When the parameter is a regular expression, it directly matches URLs that satisfy the regex validation.
- Function: When the parameter is a function, the request context will be passed to this function, and the final returned result (
true/false) will be used to determine whether there is a match.
module.exports = {
gzip: {
match(ctx) {
// Enable only for iOS devices
const reg = /iphone|ipad|ipod/i;
return reg.test(ctx.get("user-agent"));
},
},
};AI Translation | AI 翻译
This article was translated from Chinese to English by AI. If there are any inaccuracies, please refer to the original Chinese version.
本文由 AI 辅助从中文翻译为英文。如遇不准确之处,请以中文原版为准。
