So let’s go back to where we were last time, where we said:
- Perform process analysis
- Directory structure analysis
- File loading sequence
- The life cycle
- Frame built-in base object *
- Application
- Context
- Request & Response
- Controller
- Service
- Helper
- Config
- Logger
Runtime environment
A Web application itself should be stateless and have the ability to set itself according to the environment in which it runs.
Specify the operating environment
- through
config/env
File specifies that the contents of this file are the runtime environment, such asprod
- through
EGG_SERVER_ENV
Environment variable designation
Method 2 is common because it is easier to specify the runtime environment with the EGG_SERVER_ENV environment variable, such as starting the application in a production environment:
EGG_SERVER_ENV=prod npm start
Copy the code
Get the running environment in the application
Get it using app.config.env
Difference from NODE_ENV
Runtime environment and mapping supported by the framework by default (if EGG_SERVER_ENV is not specified, NODE_ENV will be matched)
NODE_ENV | EGG_SERVER_ENV | instructions |
---|---|---|
local | Local development environment | |
test | unittest | Unit testing |
production | prod | The production environment |
When NODE_ENV is production and EGG_SERVER_ENV is not specified, the framework sets EGG_SERVER_ENV to prod.
Custom Environment
While there may be more to a normal development process than the above, Egg supports custom environments to suit your own development process.
For example, add an integration test environment, SIT, to the development process. Set EGG_SERVER_ENV to sit (and NODE_ENV = production is recommended). Config /config.sit.js will be loaded on startup and the run environment variable app.config.env will be set to sit.
Differences with Koa
In Koa we use app.env to make environment judgments. App. env defaults to process.env.node_env.
In Egg (and egg-based frameworks), configuration unity is placed on app.config, so we need to distinguish environments by app.config.env, which is no longer used.
The Config configuration
The framework provides powerful and extensible configuration capabilities that automatically merge application, plug-in, and framework configurations, overwrite them sequentially, and maintain different configurations based on the environment. The combined configuration can be obtained directly from app.config.
Egg selects configuration as code, and configuration changes should be reviewed before being published. The application package itself can be deployed in multiple environments by specifying the runtime environment.
Multi-environment Configuration
The framework supports loading configuration by environment, defining configuration files for multiple environments
config
|- config.default.js
|- config.prod.js
|- config.unittest.js
|- config.local.js
Copy the code
Config.default. js is the default configuration file. This configuration file is loaded by all environments and is generally used as the default configuration file for development environments.
When env is specified, the corresponding configuration file is loaded and the configuration of the same name in the default configuration file is overwritten. If the prod environment loads config.prod.js and config.default.js files, config.prod.js overwrites the config.default.js configuration of the same name.
Configuration of writing
The configuration file returns an Object object, which can cover some configurations of the framework. Applications can also store their own service configurations here for easy management, and obtain them directly from app.config.
Export object notation
// Configure the directory for logger files. Logger is provided by the framework by default
module.exports = {
logger: {
dir: '/home/admin/logs/demoapp',}};Copy the code
The configuration file can also return a function, which is acceptableappInfo
parameter
// Put the logger directory in the code directory
const path = require('path');
module.exports = appInfo= > {
return {
logger: {
dir: path.join(appInfo.baseDir, 'logs'),}}; };Copy the code
The built-in appInfo is:
appInfo | instructions |
---|---|
pkg | package.json |
name | Application name, same as pkg.name |
baseDir | Directory of application code |
HOME | User directory, for example, the admin account is /home/admin |
root | The application root directory is baseDir only in local and Unittest environments, and HOME only in other environments. |
Appinfo.root is an elegant adaptation, such as in the server environment we would use /home/admin/logs as the log directory, but local development does not want to pollute the user directory, such an adaptation is a good solution.
Merging rules
Merge of configurations using the Extend2 module for deep copy
const a = {
arr: [ 1.2]};const b = {
arr: [ 3]}; extend(true, a, b);
// => { arr: [ 3 ] }
// The framework overwrites arrays directly instead of merging them.
Copy the code
Configure the results
Json (worker process) and run/agent_config.json (agent process), which can be used to analyze problems.
Middleware
Egg is implemented based on Koa, so the middleware form of Egg is the same as the MIDDLEWARE form of Koa, based on the Onion ring model. Every time we write a piece of middleware, we put a layer on the onion.
Writing middleware
writing
How middleware content is written
// 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 subsequent middleware execution is complete
let body = ctx.body;
if(! body)return;
if (isJSON(body)) body = JSON.stringify(body);
// Set gzip body to correct the response header
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set('Content-Encoding'.'gzip');
}
Copy the code
The framework’s middleware is written exactly the same as Koa’s middleware, so any Koa middleware can be used directly by the framework.
configuration
Typically, middleware also has its own configuration.
We agreed that a middleware is a separate file placed in the app/middleware directory that requires exports a normal function that takes two arguments:
- Options: middleware configuration items that the framework will use
app.config[${middlewareName}]
So you can get the middleware configuration directly from the configuration file - App: instance of the current Application Application
To make the gzip middleware above a simple optimization to specify that gzip compression is performed only if the body is greater than the configured threshold, create a new file gzip.js in the app/ Middleware directory
// app/middleware/gzip.js
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 subsequent middleware execution is complete
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 to correct the response header
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set('Content-Encoding'.'gzip');
};
};
Copy the code
Using middleware
After the middleware is written, we also need to manually mount it
Use middleware in your application
Add the following configuration in config.default.js to enable and configure the middleware
module.exports = {
// Configure the required middleware. The array order is the loading order of the middleware
middleware: [ 'gzip'].// Configure the gzip middleware configuration
gzip: {
threshold: 1024.// Response body less than 1k does not compress
},
// options.gzip.threshold
};
Copy the code
This configuration will eventually be merged into app.config.AppMiddleware at startup
Use middleware in frameworks and plug-ins
Frameworks and plugins do not support matching middleware in config.default.js.
// app.js
module.exports = app= > {
// Count the request time at the front of the middleware
app.config.coreMiddleware.unshift('report');
};
// app/middleware/report.js
module.exports = (a)= > {
return async function (ctx, next) {
const startTime = Date.now();
await next();
// Report the request time
reportTime(Date.now() - startTime); }};Copy the code
Middleware defined by the application layer (app.config.appMiddleware
) and the framework default middleware (app.config.coreMiddleware
) are loaded by the loader and mounted toapp.middleware
On.
The router uses middleware
The middleware configured in these two ways is global and handles every request
If you only want to apply to a single route, you can instantiate and mount it directly in app/router.js as follows:
module.exports = app= > {
const gzip = app.middleware.gzip({ threshold: 1024 });
app.router.get('/needgzip', gzip, app.controller.handler);
};
Copy the code
Framework Default middleware
In addition to the application layer loading middleware, the framework itself and other plug-ins also load a lot of middleware.
All the built-in middleware configuration items can be modified by modifying the middleware configuration items of the same name in the configuration.
The bodyParser middleware comes with the framework (the loader of the framework changes the various separators in the file name to the variable name in the form of a camel). If we want to modify the bodyParser configuration, we just need to write it in config/config.default.js
module.exports = {
bodyParser: {
jsonLimit: '10mb',}};Copy the code
Use Koa’s middleware
It is very easy to introduce the Koa middleware ecosystem within the framework.
Using koA-COMPRESS as an example, when used in KOA:
const koa = require('koa');
const compress = require('koa-compress');
const app = koa();
const options = { threshold: 2048 };
app.use(compress(options));
Copy the code
Egg
// app/middleware/compress.js
// KoA-COMPRESS exposes an interface (' (options) => middleware ') consistent with the middleware requirements of the framework
module.exports = require('koa-compress');
Copy the code
Configuring middleware
// config/config.default.js
module.exports = {
middleware: [ 'compress'].compress: {
threshold: 2048,}}Copy the code
General configuration
- Enable: controls whether the middleware is enabled
- Match: Sets that only requests that match certain rules will pass through this middleware
- Ignore: Sets requests that comply with certain rules not to pass through this middleware
Match and ignore
Match and ignore support the same parameters, but have opposite effects. Match and ignore cannot be configured at the same time.
module.exports = {
gzip: {
match: '/static',}};module.exports = {
gzip: {
match(ctx) {
// Enabled only on ios devices
const reg = /iphone|ipad|ipod/i;
return reg.test(ctx.get('user-agent')); ,}}};Copy the code
Matching rules, from egg-path-matching
const pathMatching = require('egg-path-matching');
const options = {
ignore: '/api'.// string will use parsed by path-to-regexp
// support regexp
ignore: /^\/api/.// support function
ignore: ctx= > ctx.path.startsWith('/api'),
// support Array
ignore: [ ctx= > ctx.path.startsWith('/api'), /^\/foo$/, '/bar'].// support match or ignore
match: '/api'};Copy the code
routing
Router is mainly used to describe the mapping between the request URL and the Controller that performs the specific action. The framework defines the app/router.js file to unify all routing rules.
How to define Router
app/router.js
It defines URL routing rules
// app/router.js
module.exports = app= > {
const { router, controller } = app;
router.get('/user/:id', controller.user.info);
};
Copy the code
app/controller
The Controller is implemented below the directory
// app/controller/user.js
class UserController extends Controller {
async info() {
const { ctx } = this;
ctx.body = {
name: `hello ${ctx.params.id}`}; }}Copy the code
Router definition description
The following is the complete definition of the route. Parameters can be selected according to different scenarios:
router.verb('path-match', app.controller.action);
router.verb('router-name'.'path-match', app.controller.action);
router.verb('path-match', middleware1, ... , middlewareN, app.controller.action); router.verb('router-name'.'path-match', middleware1, ... , middlewareN, app.controller.action);Copy the code
The complete definition of routing consists of five main parts:
- Verb – Indicates an action triggered by a user. It supports all HTTP methods, such as GET and POST.
router.head
– HEADrouter.options
– OPTIONSrouter.get
– GETrouter.put
– PUTrouter.post
– POSTrouter.patch
– PATCHrouter.delete
– DELETErouter.del
– Because delete is a reserved word, an alias for the delete method is provided.router.redirect
– You can redirect urls, such as the one we use most often to route the user’s access to the root directory to a home page.
- Router-name Specifies an alias for a route. You can use the Helper functions pathFor and urlFor to generate urls. (optional)
- Path-match-route URL path
- middleware1… MiddlewareN – Can have multiple Middleware configurations in the Router. (Optional), specifying that the URL path goes through only these middleware processes
- Controller – Specifies the specific controller to which routes are mapped. Controller can be written either way:
app.controller.user.fetch
– Specify a specific controller directly'user.fetch'
– Can be shortened to a string
Matters needing attention
- In the Router definition, multiple Middleware implementations can be supported in tandem
- Controller must be defined in the app/ Controller directory
- A file can also contain multiple Controller definitions, which can be passed when defining routes
${fileName}.${functionName}
To specify the corresponding Controller. - The Controller supports subdirectories that can be passed when defining routes
${directoryName}.${fileName}.${functionName}
To specify the corresponding Controller.
demo
// app/router.js
module.exports = app= > {
const { router, controller } = app;
router.get('/home', controller.home);
router.get('/user/:id', controller.user.page);
router.post('/admin', isAdmin, controller.admin);
router.post('/user', isLoginUser, hasAdminPermission, controller.user.create);
router.post('/api/v1/comments', controller.v1.comments.create); // app/controller/v1/comments.js
};
Copy the code
RESTful URL definition
Provides app.resources(‘routerName’, ‘pathMatch’, Controller) to quickly generate CRUD routing structures on a path.
// app/router.js
module.exports = app= > {
const { router, controller } = app;
router.resources('posts'.'/api/posts', controller.posts);
router.resources('users'.'/api/v1/users', controller.v1.users); // app/controller/v1/users.js
};
Copy the code
The code above deploys a set of CRUD path structures on the /posts path, corresponding to Controller app/ Controller /posts.js. Then, you just need to implement the corresponding function in posts.js.
Method | Path | Route Name | Controller.Action |
---|---|---|---|
GET | /posts | posts | app.controllers.posts.index |
GET | /posts/new | new_post | app.controllers.posts.new |
GET | /posts/:id | post | app.controllers.posts.show |
GET | /posts/:id/edit | edit_post | app.controllers.posts.edit |
POST | /posts | posts | app.controllers.posts.create |
PUT | /posts/:id | post | app.controllers.posts.update |
DELETE | /posts/:id | post | app.controllers.posts.destroy |
// app/controller/posts.js
exports.index = async() = > {}; exports.new =async() = > {}; exports.create =async() = > {}; exports.show =async() = > {}; exports.edit =async() = > {}; exports.update =async() = > {}; exports.destroy =async() = > {};Copy the code
If we don’t need any of these methods, we don’t need to implement them in posts.js, so that the URL path is not registered with the Router.
The router of actual combat
Parameters to obtain
The Query String way
ctx.query.xxx
// app/router.js
module.exports = app= > {
app.router.get('/search', app.controller.search.index);
};
// app/controller/search.js
exports.index = async ctx => {
ctx.body = `search: ${ctx.query.name}`;
};
/ / curl http://127.0.0.1:7001/search? name=egg
Copy the code
Parameter naming
/user/:id/:name
=> ctx.params.xxx
// app/router.js
module.exports = app= > {
app.router.get('/user/:id/:name', app.controller.user.info);
};
// app/controller/user.js
exports.info = async ctx => {
ctx.body = `user: ${ctx.params.id}.${ctx.params.name}`;
};
/ / curl http://127.0.0.1:7001/user/123/xiaoming
Copy the code
Complex parameter acquisition
Routing also supports the definition of re, which can be more flexible to obtain parameters:
// app/router.js
module.exports = app= > {
app.router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
};
// app/controller/package.js
exports.detail = async ctx => {
// If the request URL is matched by the re, it can be retrieved from ctx.params in the order of the capture groups.
// The content of 'ctx.params[0]' is' egg/1.0.0 'as requested below
ctx.body = `package:${ctx.params[0]}`;
};
/ / curl http://127.0.0.1:7001/package/egg/1.0.0
Copy the code
Fetching form content
// app/router.js
module.exports = app= > {
app.router.post('/form', app.controller.form.post);
};
// app/controller/form.js
exports.post = async ctx => {
ctx.body = `body: The ${JSON.stringify(ctx.request.body)}`;
};
Copy the code
Here a direct POST request will result in an error.
The above verification is due to the built-in egg-Security plug-in in the framework, which provides some default security practices. Security plug-ins of the framework are enabled by default. If you want to disable some security defenses, you can directly set the enable attribute of this item to false.
Shut down CSRF temporarily
exports.security = {
csrf: false
};
Copy the code
Form validation
npm i -S egg-validate
Copy the code
// config/plugin.js
module.exports = {
// had enabled by egg
// static: {
// enable: true,
// }
validate: {
enable: true.package: 'egg-validate',}};Copy the code
// app/router.js
module.exports = app= > {
app.router.resources('/user', app.controller.user);
};
// app/controller/user.js
const createRule = {
username: {
type: 'email',},password: {
type: 'password'.compare: 're-password',}}; exports.create =async ctx => {
// If the verification fails, an exception will be thrown
ctx.validate(createRule);
ctx.body = ctx.request.body;
};
Copy the code
redirect
Internal redirection
// app/router.js
module.exports = app= > {
app.router.get('index'.'/home/index', app.controller.home.index);
app.router.redirect('/'.'/home/index'.303); // Access/automatically redirects to /home/index
};
// app/controller/home.js
exports.index = async ctx => {
ctx.body = 'hello controller';
};
Copy the code
External redirection
exports.index = async ctx => {
const type = ctx.query.type;
const q = ctx.query.q || 'nodejs';
if (type === 'bing') {
ctx.redirect(`http://cn.bing.com/search?q=${q}`);
} else {
ctx.redirect(`https://www.google.co.kr/search?q=${q}`); }};Copy the code
Controller
The Controller is responsible for parsing the user’s input, processing it and returning the result.
The framework recommends that the Controller layer mainly processes the user’s request parameters (verification and conversion), then invokes the corresponding Service method to process the business, and encapsulates and returns the business results after obtaining them:
- Gets the request parameters passed by the user over HTTP
- Calibration and assembly parameters
- The Service is called for business processing and, if necessary, the return result of the transformed Service is processed to suit the needs of the user
- The result is returned to the user via HTTP
How to write Controller
All Controller files must be stored in the app/ Controller directory. Multi-level directories can be supported. Access can be cascaded by directory name
Controller class (recommended)
// app/controller/post.js
const Controller = require('egg').Controller;
class PostController extends Controller {
async create() {
const { ctx } = this;
ctx.body = 'PostController';
ctx.status = 201; }}module.exports = PostController;
Copy the code
The method defined above can be located by filename and method name in the route via app.controller
// app/router.js
module.exports = app= > {
const { router, controller } = app;
router.post('createPost'.'/api/posts', controller.post.create);
}
Copy the code
Controller supports multiple directories, for example, if we put the Controller code in app/Controller/sub/post js, then
app.router.post('createPost'.'/api/posts', app.controller.sub.post.create);
Copy the code
The Controller class defined by the properties description instantiates a brand new object with each request to the server, while the Controller class in the project inherits from egg.controller and has the following properties attached to this.
this.ctx
: An instance of the current request Context object, through which we can get convenience properties and methods wrapped in the framework to handle the current request.this.app
: An instance of the current Application object, from which we can get global objects and methods provided by the framework.this.service
: Apply the defined Service, through which we can access the abstract business layer, equivalent to this.ctx.service.this.config
: Indicates the configuration item of the application runtime.this.logger
: logger object, which has four methods (debug
.info
.warn
.error
) indicates that four levels of logs are printed. The method and effect are the same as those described in Context Logger, but logs recorded by this Logger object are preceded by the path of the log file to quickly locate the log location.
Customize the Controller base class
// app/core/base_controller.js
const { Controller } = require('egg');
class BaseController extends Controller {
get user() {
return this.ctx.session.user;
}
success(data) {
this.ctx.body = {
success: true,
data,
};
}
notFound(msg) {
msg = msg || 'not found';
this.ctx.throw(404, msg); }}module.exports = BaseController;
Copy the code
BaseController: BaseController: BaseController: BaseController: BaseController: BaseController
//app/controller/post.js
const Controller = require('.. /core/base_controller');
class PostController extends Controller {
async list() {
const posts = await this.service.listByUser(this.user); // Use base class methods
this.success(posts); // Use base class methods}}Copy the code
The Controller method
Each Controller is an Async function whose input is an instance of the requested Context object, through which we can get various convenient properties and methods encapsulated by the framework.
// app/controller/post.js
exports.create = async ctx => {
const createRule = {
title: { type: 'string' },
content: { type: 'string'}};// Check parameters
ctx.validate(createRule);
// Assemble parameters
const author = ctx.session.userId;
const req = Object.assign(ctx.request.body, { author });
// Call service for business processing
const res = await ctx.service.post.create(req);
// Set the response content and response status code
ctx.body = { id: res.id };
ctx.status = 201;
};
Copy the code
Gets HTTP request parameters
query
class PostController extends Controller {
async listPosts() {
const query = this.ctx.query;
/ / {
// category: 'egg',
// language: 'node',
// }}}Copy the code
When a key is duplicated in a Query String, ctx. Query takes only the value of the first occurrence of the key, and subsequent occurrences are ignored.
queries
Sometimes our system is designed to have users pass the same key, like GET /posts? Category = egg&id = 1 & id & id = 3 = 2.
The racks provide the CTX. queries object, which also parses the Query String, but does not discard any duplicate data. Instead, it puts them all in an array.
// GET /posts? category=egg&id=1&id=2&id=3
class PostController extends Controller {
async listPosts() {
console.log(this.ctx.queries);
/ / {
// category: [ 'egg' ],
// id: [ '1', '2', '3' ],
// }}}Copy the code
Router params
The Router can also declare parameters, which can be obtained through ctx.params.
// app.get('/projects/:projectId/app/:appId', 'app.listApp');
// GET /projects/1/app/2
class AppController extends Controller {
async listApp() {
assert.equal(this.ctx.params.projectId, '1');
assert.equal(this.ctx.params.appId, '2'); }}Copy the code
body
BodyParser middleware is built into the framework to parse the request body of these two formats into object and mount it to CTx.request.body.
In general, the most frequently adjusted configuration item is to change the maximum length allowed for parsing, which can be overridden by the framework’s default values in config/config.default.js.
module.exports = {
bodyParser: {
jsonLimit: '1mb'.formLimit: '1mb',}};Copy the code
If the user’s Request body exceeds our configured maximum parsing length, an exception with status code 413 (Request Entity Too Large) will be thrown. If the user’s Request body parsing fails (wrong JSON), Raises an exception with a status code of 400 (Bad Request).
A common mistake is to confuse ctx.request.body with ctx.body, which is short for ctx.Response. body.
Get the uploaded file
Typically, browsers send files in Multipart/form-data format, and the framework supports retrieving user-uploaded files through the built-in Multipart plugin.
The File mode:
Enable file mode in config file:
// config/config.default.js
exports.multipart = {
mode: 'file'};Copy the code
Upload/Receive files:
Upload/receive a single file:
<form method="POST" action="/upload? _csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file: <input name="file" type="file" />
<button type="submit">Upload</button>
</form>
Copy the code
The corresponding back-end code is as follows:
// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('mz/fs');
module.exports = class extends Controller {
async upload() {
const { ctx } = this;
const file = ctx.request.files[0];
const name = 'egg-multipart-test/' + path.basename(file.filename);
let result;
try {
// Process files, such as uploading to the cloud
result = await ctx.oss.put(name, file.filepath);
} finally {
// Temporary files need to be deleted
await fs.unlink(file.filepath);
}
ctx.body = {
url: result.url,
// Get all field valuesrequestBody: ctx.request.body, }; }};Copy the code
Upload/receive multiple files:
For multiple files, we iterate with the ctx.request.files property and then process them separately:
<form method="POST" action="/upload? _csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file1: <input name="file1" type="file" />
file2: <input name="file2" type="file" />
<button type="submit">Upload</button>
</form>
Copy the code
Corresponding back-end code:
// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('mz/fs');
module.exports = class extends Controller {
async upload() {
const { ctx } = this;
console.log(ctx.request.body);
console.log('got %d files', ctx.request.files.length);
for (const file of ctx.request.files) {
console.log('field: ' + file.fieldname);
console.log('filename: ' + file.filename);
console.log('encoding: ' + file.encoding);
console.log('mime: ' + file.mime);
console.log('tmp filepath: ' + file.filepath);
let result;
try {
// Process files, such as uploading to the cloud
result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
} finally {
// Temporary files need to be deleted
await fs.unlink(file.filepath);
}
console.log(result); }}};Copy the code
To ensure file upload security, the framework limits the supported file formats. By default, the framework supports the following whitelist:
// images
'.jpg'.'.jpeg'.// image/jpeg
'.png'.// image/png, image/x-png
'.gif'.// image/gif
'.bmp'.// image/bmp
'.wbmp'.// image/vnd.wap.wbmp
'.webp'.'.tif'.'.psd'.// text
'.svg'.'.js'.'.jsx'.'.json'.'.css'.'.less'.'.html'.'.htm'.'.xml'.// tar
'.zip'.'.gz'.'.tgz'.'.gzip'.// video
'.mp3'.'.mp4'.'.avi'.Copy the code
Users can add supported file extensions by configuring them in config/config.default.js, or rewrite the entire whitelist.
- Added supported file name extensions
module.exports = {
multipart: {
fileExtensions: [ '.apk' ] // Add file support for the APk extension}};Copy the code
- Overwrite the entire whitelist
module.exports = {
multipart: {
whitelist: [ '.png'].// Overwrite the entire whitelist, only upload '.png' format is allowed}};Copy the code
See the Egg – Multipart
header
In addition to getting parameters from the URL and request body, many parameters are passed through the request header.
ctx.headers
.ctx.header
.ctx.request.headers
.ctx.request.header
: These methods are equivalent, they are all fetchThe entire header object.ctx.get(name)
.ctx.request.get(name)
: Gets the value of a field in the request header, and returns an empty string if the field does not exist.- We suggest using
ctx.get(name)
Rather thanctx.headers['name']
Because the former automatically handles case.
Cookie
Through CTx. cookies, we can conveniently and securely set and read cookies in Controller.
class CookieController extends Controller {
async add() {
const ctx = this.ctx;
const count = ctx.cookies.get('count');
count = count ? Number(count) : 0;
ctx.cookies.set('count', ++count);
ctx.body = count;
}
async remove() {
const ctx = this.ctx;
const count = ctx.cookies.set('count'.null);
ctx.status = 204; }}Copy the code
Session
Through cookies, we can set up a Session for each user, which is used to store user identity-related information. This information will be encrypted and stored in cookies to maintain user identity across requests.
The framework has a built-in Session plugin, which provides ctx. Session to access or modify the current user Session.
class PostController extends Controller {
async fetchPosts() {
const ctx = this.ctx;
// Get the contents of Session
const userId = ctx.session.userId;
const posts = await ctx.service.post.fetch(userId);
// Change the Session value
ctx.session.visited = ctx.session.visited ? ++ctx.session.visited : 1;
ctx.body = {
success: true, posts, }; }}Copy the code
Session is a very straightforward way to use it, just read it or modify it, if you want to delete it, just assign it null:
class SessionController extends Controller {
async deleteSession() {
this.ctx.session = null; }};Copy the code
configuration
module.exports = {
key: 'EGG_SESS'.// The name of the Cookie key-value pair hosting the Session
maxAge: 86400000.// Maximum duration of Session
};
Copy the code
Parameter calibration
Validate plug-in provides a convenient parameter verification mechanism to help us complete various complex parameter verification.
Since it is a plug-in, we need to complete the registration of the plug-in:
// config/plugin.js
exports.validate = {
enable: true.package: 'egg-validate'};Copy the code
Ctx.validate (rule, [body])
this.ctx.validate({
title: { type: 'string' },
content: { type: 'string'}});Copy the code
When verifying an exception, an exception is directly thrown. The status code of the exception is 422 (Unprocessable Entity). The errors field contains detailed verification failure information. If you want to handle checked exceptions yourself, you can catch them yourself with a try catch.
class PostController extends Controller {
async create() {
const ctx = this.ctx;
try {
ctx.validate(createRule);
} catch (err) {
ctx.logger.warn(err.errors);
ctx.body = { success: false };
return; }}};Copy the code
Validation rules
Parameter verification is completed through Parameter. The supported verification rules can be found in the documentation of the module.
You can add a custom rule by running app.validator.addRule(type, check).
// app.js
app.validator.addRule('json', (rule, value) => {
try {
JSON.parse(value);
} catch (err) {
return 'must be json string'; }});Copy the code
Call the Service
The Controller can call any method on any Service, and the Service is lazily loaded and instantiated only when it is accessed.
Sending an HTTP response
Set the status
ctx.status = 201;
Copy the code
Set up the body
The vast majority of data is sent to the requester through the body in the request. Just like the body in the request, the body sent in the response needs to have a matching Content-Type that tells the client how to parse the data.
Ctx. body is short for ctx.response.body, not to be confused with ctx.request.body.
class ViewController extends Controller {
async show() {
this.ctx.body = {
name: 'egg'.category: 'framework'.language: 'Node.js'}; }async page() {
this.ctx.body = '<html><h1>Hello</h1></html>'; }}Copy the code
Due to the streaming nature of Node.js, there are many scenarios that require a Stream response, such as returning a large file, the proxy server directly returning upstream content, and the framework also supports setting the body as a Stream and handling error events on the Stream.
class ProxyController extends Controller {
async proxy() {
const ctx = this.ctx;
const result = await ctx.curl(url, {
streaming: true}); ctx.set(result.header);// result.res is a streamctx.body = result.res; }};Copy the code
Set the Header
Ctx. set(key, value) allows you to set one response Header, and ctx.set(headers) allows you to set multiple headers.
redirect
The framework overrides KOA’s native CTx. redirect implementation with a Security plug-in to provide more secure redirection.
ctx.redirect(url)
If the domain name is not in the configured whitelist, the hop is prohibited.ctx.unsafeRedirect(url)
Skip to another domain name without checking the domain name. Use the domain name after knowing the risks.
To use the CTx. redirect method, you need to configure the following parameters in the application configuration file:
// config/config.default.js
exports.security = {
domainWhiteList: ['.domain.com'].// Security whitelist, starting with
};
Copy the code
If domainWhiteList is not configured or the array is empty, all forward requests will be allowed by default. This is the same as ctx.unsaferedirect (url).