最強express 講解--官網例子最全
Application
express()
Create an express application.
var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
settings
The following settings will alter how Express behaves:
trust proxy
Indicate that the app is sitting behind a front-facing proxy, and theX-Forwarded-*
headers may be trusted for determining the connection and the IP address of the client. It must, however, be noted that, theX-Forwarded-*
headers are easily spoofed and the detected IP addresses are unreliable.trust proxy
is disabled by default. When enabled, Express attempts to determine the IP address of the client which is connected through the front-facing proxy, or a series of proxies. Thereq.ips
property, then, contains an array of IP addresses the client is connected through. It can be enabled using either of the following values.Type Value Boolean If
true
, the client's IP address is understood as the left-most entry in theX-Forwarded-*
header.If
false
, the app is understood as directly facing the Internet and the client's IP address is derived fromreq.connection.remoteAddress
. This is the default setting.IP addresses An IP address, subnet, or an array of IP addresses, and subnets to trust. The following is the list of pre-configured subnet names.
- loopback -
127.0.0.1/8
,::1/128
- linklocal -
169.254.0.0/16
,fe80::/10
- uniquelocal -
10.0.0.0/8
,172.16.0.0/12
,192.168.0.0/16
,fc00::/7
The IP addresses can be set in the following ways.
app.set('trust proxy', 'loopback') // specify a single subnet app.set('trust proxy', 'loopback, 123.123.123.123') // specify a subnet and an address app.set('trust proxy', 'loopback, linklocal, uniquelocal') // specify multiple subnets as CSV app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']) // specify multiple subnets as an array
When specified, the IP addresses or the subnets are excluded from the address determination process, and the untrusted IP address nearest to the application server is determined as the client's IP address.
Number Trust the n
th hop from the front-facing proxy server as the client.Function Custom trust implementation. Use this only if you know what you are doing. app.set('trust proxy', function (ip) { if (ip === '127.0.0.1' || ip === '123.123.123.123') return true; // trusted IPs else return false; })
The
trust proxy
setting is implemented using the proxy-addr package, look up its documentation for further details.- loopback -
env
Environment mode, defaults toprocess.env.NODE_ENV
(NODE_ENV environment variable) or "development"subdomain offset
The number of dot-separated parts of the host to remove to access subdomain, two by defaultjsonp callback name
Changes the default callback name of?callback=
json replacer
JSON replacer callback, null by defaultcase sensitive routing
Enable case sensitivity, disabled by default, treating "/Foo" and "/foo" as the samestrict routing
Enable strict routing, by default "/foo" and "/foo/" are treated the same by the routerview cache
Enables view template compilation caching, enabled in production by defaultview engine
The default engine extension to use when omittedviews
The view directory path, defaulting to "process.cwd() + '/views'"query parser
The query parser to use - "simple" or "extended", defaults to "extended". The simple query parser is based on node's native query parser, querystring. The extended query parser is based on qp.x-powered-by
Enables theX-Powered-By: Express
HTTP header, enabled by defaultetag
Set the ETag response header.Type Value Boolean true
enables strong ETag. This is the default setting.
false
disables ETag altogether.String "strong", enables strong ETag.
"weak", enables weak ETag.Function Custom ETag function implementation. Use this only if you know what you are doing. app.set('etag', function (body, encoding) { return generateHash(body, encoding); // consider the function is defined })
app.set(name, value)
Assigns setting name
to value
.
app.set('title', 'My Site');
app.get('title');
// => "My Site"
app.get(name)
Get setting name
value.
app.get('title');
// => undefined
app.set('title', 'My Site');
app.get('title');
// => "My Site"
app.enable(name)
Set setting name
to true
.
app.enable('trust proxy');
app.get('trust proxy');
// => true
app.disable(name)
Set setting name
to false
.
app.disable('trust proxy');
app.get('trust proxy');
// => false
app.enabled(name)
Check if setting name
is enabled.
app.enabled('trust proxy');
// => false
app.enable('trust proxy');
app.enabled('trust proxy');
// => true
app.disabled(name)
Check if setting name
is disabled.
app.disabled('trust proxy');
// => true
app.enable('trust proxy');
app.disabled('trust proxy');
// => false
app.use([path], [function...], function)
Mount the middleware function
(s)
at the path
. If path
is
not specified, it defaults to "/".
Mounting a middleware at a path
will cause
the middleware function to be executed whenever the base of the requested path matches the path
.
Since path
defaults to "/", middleware mounted
without a path will be executed for every request to the app.
// this middleware will be executed for every request to the app
app.use(function (req, res, next) {
console.log('Time: %d', Date.now());
next();
})
Middleware functions are executed sequentially, therefore the order of middleware inclusion is important.
// this middleware will not allow the request to go beyond it
app.use(function(req, res, next) {
res.send('Hello World');
})
// requests will never reach this route
app.get('/', function (req, res) {
res.send('Welcome');
})
path
can be a string representing a path,
a path pattern, a regular expression to match paths, or an array of combinations of the aforementioned path objects.
Type | Example |
---|---|
Path |
|
Path Pattern |
|
Regular Expression |
|
Array |
|
function
can be a middleware function, a series
of middleware functions, an array of middleware functions, or a combination of all of them. Since routers and apps implement the middleware interface, they can be used like any other middleware function.
Usage | Example |
---|---|
Single Middleware |
A middleware function can be defined and mounted locally.
A router is a valid middleware.
An Express app is a valid middleware.
|
Series of Middleware |
More than one middleware can be specified at a mount path.
|
Array |
Clubbing middleware in arrays is a good way to logically group them. The mount path has to be specified, if an array of middleware is passed as the first or the only set of middleware.
|
Combination |
All the above ways of mounting middleware can be combined.
|
Following are some examples of using the express.static middleware in an Express app.
Serve static content for the app from the "public" directory in the application directory.
// GET /style.css etc
app.use(express.static(__dirname + '/public'));
Mount the middleware at "/static" to server static content only when their request path is prefixed with "/static".
// GET /static/style.css etc.
app.use('/static', express.static(__dirname + '/public'));
Disable logging for static content requests by loading the logger middleware after the static middleware.
app.use(express.static(__dirname + '/public'));
app.use(logger());
Serve static files from multiple directories, but give precedence to "./public" over the others.
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads'));
app.engine(ext, callback)
Register the given template engine callback
as ext
By
default will require()
the engine based on the file extension. For example if you try to render a "foo.jade"
file Express will invoke the following internally, and cache the require()
on subsequent calls to increase
performance.
app.engine('jade', require('jade').__express);
For engines that do not provide .__express
out
of the box - or if you wish to "map" a different extension to the template engine you may use this method. For example mapping the EJS template engine to ".html" files:
app.engine('html', require('ejs').renderFile);
In this case EJS provides a .renderFile()
method
with the same signature that Express expects: (path, options, callback)
, though note that it aliases
this method as ejs.__express
internally so if you're using ".ejs" extensions you dont need to do anything.
Some template engines do not follow this convention, the consolidate.js library was created to map all of node's popular template engines to follow this convention, thus allowing them to work seemlessly within Express.
var engines = require('consolidate');
app.engine('haml', engines.haml);
app.engine('html', engines.hogan);
app.param([name], callback)
Map logic to route parameters. For example, when :user
is
present in a route path, you may map user loading logic to automatically provide req.user
to the route,
or perform validations on the parameter input.
Note
- Param callback functions are local to the router on which they are defined. They are not inherited by mounted apps or routers. Hence, param callbacks defined on
app
will be trigerred only by route parameters defined onapp
routes. - A param callback will be called only once in a request-response cycle, even if the parameter is matched in multiple routes.
app.param('id', function (req, res, next, id) { console.log('CALLED ONLY ONCE'); next(); }) app.get('/user/:id', function (req, res, next) { console.log('although this matches'); next(); }); app.get('/user/:id', function (req, res) { console.log('and this matches too'); res.end(); });
The following snippet illustrates how the callback
is
much like middleware, thus supporting async operations. However, it provides the additional value of the parameter (here named as id
),
derived from the corresponding parameter in thereq.params
object. An attempt to load the user is then
performed, assigningreq.user
; otherwise an error is passed to next(err)
.
app.param('user', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
next(err);
} else if (user) {
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
Alternatively, you can pass only a callback
,
in which case you have the opportunity to alter the app.param()
API. For example the express-params defines
the following callback which allows you to restrict parameters to a given regular expression.
This example is a bit more advanced. It is checking if the second argument is a regular expression, returning the callback, which acts much like the "user" param example.
app.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
});
The method could now be used to effectively validate parameters (and optionally parse them to provide capture groups):
app.param('id', /^\d+$/);
app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
app.param('range', /^(\w+)\.\.(\w+)?$/);
app.get('/range/:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
app.VERB(path, [callback...], callback)
The app.VERB()
methods provide the routing
functionality in Express, where VERB is one of the HTTP verbs (such as app.post()
).
Multiple callbacks may be given; all are treated equally, and behave just like middleware. The only exception is that these callbacks may invoke next('route')
to
bypass the remaining route callback(s). This mechanism can be used to perform pre-conditions on a route, then pass control to subsequent routes if there's no reason to proceed with the current route.
The following snippet illustrates the most simple route definition possible. Express translates the path strings to regular expressions, used internally to match incoming requests. Query strings are not considered when peforming these matches. For example, "GET /" would match the following route, as would "GET /?name=tobi":
app.get('/', function(req, res){
res.send('hello world');
});
Regular expressions may also be used, and can be useful if you have very specific restraints, for example the following would match "GET /commits/71dbb9c" as well as "GET /commits/71dbb9c..4c084f9".
app.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
var from = req.params[0];
var to = req.params[1] || 'HEAD';
res.send('commit range ' + from + '..' + to);
});
Several callbacks may also be passed, useful for re-using middleware that load resources, perform validations, etc.
app.get('/user/:id', user.load, function(){
// ...
})
If you have multiple common middleware for a route, you can use the route API withall
.
var middleware = [loadForum, loadThread];
app.route('/forum/:fid/thread/:tid')
.all(loadForum)
.all(loadThread)
.get(function() { //... });
.post(function() { //... });
Both middleware will be run for GET and POST requests.
app.all(path, [callback...], callback)
This method functions just like the app.VERB()
methods,
however it matches all HTTP verbs.
This method is extremely useful for mapping "global" logic for specific path prefixes or arbitrary matches. For example if you placed the following route at the top of all other route definitions,
it would require that all routes from that point on would require authentication, and automatically load a user. Keep in mind that these callbacks do not have to act as end points, loadUser
can
perform a task, then next()
to continue matching subsequent routes.
app.all('*', requireAuthentication, loadUser);
Or the equivalent:
app.all('*', requireAuthentication)
app.all('*', loadUser);
Another great example of this is white-listed "global" functionality. Here the example is much like before, however only restricting paths prefixed with "/api":
app.all('/api/*', requireAuthentication);
app.route(path)
Returns an instance of a single route, which can then be used to handle HTTP verbs with optional middleware. Using app.route()
is
a recommended approach for avoiding duplicate route names (and thus typo errors).
var app = express();
app.route('/events')
.all(function(req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware!
})
.get(function(req, res, next) {
res.json(...);
})
.post(function(req, res, next) {
// maybe add a new event...
})
app.locals
Application local variables are provided to all templates rendered within the application. This is useful for providing helper functions to templates, as well as app-level data.
app.locals.title = 'My App';
app.locals.strftime = require('strftime');
app.locals.email = 'me@myapp.com';
The app.locals
object is a JavaScript Object
.
The properties added to it will be exposed as local variables within the application.
app.locals.title
// => 'My App'
app.locals.email
// => 'me@myapp.com'
By default, Express exposes only a single app-level local variable: settings
.
app.set('title', 'My App');
// use settings.title in a view
app.render(view, [options], callback)
Render a view
with a callback responding with
the rendered string. This is the app-level variant of res.render()
, and otherwise behaves the same way.
app.render('email', function(err, html){
// ...
});
app.render('email', { name: 'Tobi' }, function(err, html){
// ...
});
app.listen()
Bind and listen for connections on the given host and port. This method is identical to node's http.Server#listen().
var express = require('express');
var app = express();
app.listen(3000);
The app
returned by express()
is
in fact a JavaScript Function
, designed to be passed to node's HTTP servers as a callback to handle requests.
This allows you to provide both HTTP and HTTPS versions of your app with the same codebase easily, as the app does not inherit from these (it is simply a callback):
var express = require('express');
var https = require('https');
var http = require('http');
var app = express();
http.createServer(app).listen(80);
https.createServer(options, app).listen(443);
The app.listen()
method is a convenience method
for the following (if you wish to use HTTPS or provide both, use the technique above):
app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
app.path()
Returns the canonical path of the app.
var app = express()
, blog = express()
, blogAdmin = express();
app.use('/blog', blog);
blog.use('/admin', blogAdmin);
console.log(app.path()); // ''
console.log(blog.path()); // '/blog'
console.log(blogAdmin.path()); // '/blog/admin'
The behavior of this method can become very complicated in complex cases of mounted apps, hence it is recommended to use req.baseUrl
to
get the canonical path of the app.
app.mountpath
This property refers to the path pattern(s) on which a sub app was mounted.
var admin = express();
admin.get('/', function (req, res) {
console.log(admin.mountpath); // /admin
res.send('Admin Homepage');
})
app.use('/admin', admin); // mount the sub app
It is similar to the baseUrl
property
of the req
object, except req.baseUrl
returns
the matched URL path, instead of the matched pattern(s).
If a sub app is mounted on multiple path patterns, app.mountpath
returns
the list of patterns it is mounted on, as shown in the following example.
var admin = express();
admin.get('/', function (req, res) {
console.log(admin.mountpath); // [ '/adm*n', '/manager' ]
res.send('Admin Homepage');
})
var secret = express();
secret.get('/', function (req, res) {
console.log(secret.mountpath); // /secr*t
res.send('Admin Secret');
});
admin.use('/secr*t', secret); // load the 'secret' router on '/secr*t', on the 'admin' sub app
app.use(['/adm*n', '/manager'], admin); // load the 'admin' router on '/adm*n' and '/manager', on the parent app
app.on('mount', callback(parent))
The mount
event is fired on a sub app, when
it is mounted on a parent app. The parent app is passed to the callback function.
var admin = express();
admin.on('mount', function (parent) {
console.log('Admin Mounted');
console.log(parent); // refers to the parent app
});
admin.get('/', function (req, res) {
res.send('Admin Homepage');
});
app.use('/admin', admin);
Request
req.params
This property is an object containing properties mapped to the named route "parameters". For example, if you have the route /user/:name
,
then the "name" property is available to you as req.params.name
. This object defaults to {}
.
// GET /user/tj
req.params.name
// => "tj"
When a regular expression is used for the route definition, capture groups are provided in the array using req.params[N]
,
where N
is the nth capture group. This rule is applied to unnamed wild-card matches with string routes
such as `/file/*`:
// GET /file/javascripts/jquery.js
req.params[0]
// => "javascripts/jquery.js"
req.query
This property is an object containing the parsed query-string, defaulting to {}
.
// GET /search?q=tobi+ferret
req.query.q
// => "tobi ferret"
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse"
req.param(name)
Return the value of param name
when present.
// ?name=tobi
req.param('name')
// => "tobi"
// POST name=tobi
req.param('name')
// => "tobi"
// /user/tobi for /user/:name
req.param('name')
// => "tobi"
Lookup is performed in the following order:
req.params
req.body
req.query
Direct access to req.body
, req.params
,
and req.query
should be favoured for clarity - unless you truly accept input from each object.
req.route
The currently matched Route
.
app.get('/user/:id?', function userIdHandler(req, res){
console.log(req.route);
res.send('GET');
})
Example output from the previous snippet:
{ path: '/user/:id?',
stack:
[ { handle: [Function: userIdHandler],
name: 'userIdHandler',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i,
method: 'get' } ],
methods: { get: true } }
req.cookies
When the cookieParser()
middleware is used,
this object defaults to {}
. Otherwise, it contains the cookies sent by the user-agent.
// Cookie: name=tj
req.cookies.name
// => "tj"
Please refer to cookie-parser for additional documentation or any issues and concerns.
req.signedCookies
When the cookieParser(secret)
middleware is
used, this object defaults to {}
. Otherwise, it contains the signed cookies sent by the user-agent, unsigned
and ready for use. Signed cookies reside in a different object to show developer intent; otherwise, a malicious attack could be placed on `req.cookie` values (which are easy to spoof). Note that signing a cookie does not make it "hidden" or encrypted; this
simply prevents tampering (because the secret used to sign is private).
// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3
req.signedCookies.user
// => "tobi"
Please refer to cookie-parser for additional documentation or any issues and concerns.
req.get(field)
Get the case-insensitive request header field
.
The Referrer and Referer fields are interchangeable.
req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined
Aliased as req.header(field)
.
req.accepts(types)
Check if the given types
are acceptable, returning
the best match when true, or elseundefined
(in which case you should respond with 406 "Not Acceptable").
The type
value may be a single mime type string
(such as "application/json"), the extension name such as "json", a comma-delimited list, or an array. When a list or array is given, the best match (if any) is returned.
// Accept: text/html
req.accepts('html');
// => "html"
// Accept: text/*, application/json
req.accepts('html');
// => "html"
req.accepts('text/html');
// => "text/html"
req.accepts('json, text');
// => "json"
req.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined
// Accept: text/*;q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => "json"
Please refer to accepts for additional documentation or any issues and concerns.
req.acceptsCharsets(charset, ...)
Check if the given charset
are acceptable.
Please refer to accepts for additional documentation or any issues and concerns.
req.acceptsLanguages(lang, ...)
Check if the given lang
are acceptable.
Please refer to accepts for additional documentation or any issues and concerns.
req.acceptsEncodings(encoding, ...)
Check if the given encoding
are acceptable.
Please refer to accepts for additional documentation or any issues and concerns.
req.is(type)
Check if the incoming request contains the "Content-Type" header field, and if it matches the give mime type
.
// With Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true
// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false
Please refer to type-is for additional documentation or any issues and concerns.
req.ip
Return the remote address (or, if "trust proxy" is enabled, the upstream address).
req.ip
// => "127.0.0.1"
req.ips
When "trust proxy" is `true`, parse the "X-Forwarded-For" ip address list and return an array. Otherwise, an empty array is returned. For example, if the value were "client, proxy1, proxy2", you would
receive the array ["client", "proxy1", "proxy2"]
, where "proxy2" is the furthest down-stream.
req.path
Returns the request URL pathname.
// example.com/users?sort=desc
req.path
// => "/users"
req.hostname
Returns the hostname from the "Host" header field.
// Host: "example.com:3000"
req.hostname
// => "example.com"
req.fresh
Check if the request is "fresh" (i.e. whether the Last-Modified and/or the ETag still match).
req.fresh
// => true
Please refer to fresh for additional documentation or any issues and concerns.
req.stale
Check if the request is "stale" (i.e. the Last-Modified and/or ETag headers do not match).
req.stale
// => true
req.xhr
Check if the request was issued with the "X-Requested-With" header field set to "XMLHttpRequest" (jQuery etc).
req.xhr
// => true
req.protocol
Return the protocol string "http" or "https" when requested with TLS. If the "trust proxy" setting is enabled, the "X-Forwarded-Proto" header field will be trusted. If you're running behind a reverse proxy that supplies https for you, this may be enabled.
req.protocol
// => "http"
req.secure
Check if a TLS connection is established. This is a short-hand for:
'https' == req.protocol;
req.subdomains
Return subdomains as an array.
// Host: "tobi.ferrets.example.com"
req.subdomains
// => ["ferrets", "tobi"]
req.originalUrl
This property is much like req.url
; however,
it retains the original request url, allowing you to rewrite req.url
freely for internal routing purposes.
For example, the "mounting" feature of app.use() will rewrite req.url
to
strip the mount point.
// GET /search?q=something
req.originalUrl
// => "/search?q=something"
req.baseUrl
This property refers to the URL path, on which a router instance was mounted.
var greet = express.Router();
greet.get('/jp', function (req, res) {
console.log(req.baseUrl); // /greet
res.send('Konichiwa!');
});
app.use('/greet', greet); // load the router on '/greet'
Even if a path pattern or a set of path patterns were used to load the router, the matched string is returned as the baseUrl
,
instead of the pattern(s). In the following example, the greet
router is loaded on two path patterns.
app.use(['/gre+t', '/hel{2}o'], greet); // load the router on '/gre+t' and '/hel{2}o'
When the request is made to /greet/jp
, req.baseUrl
will
be equal to "/greet"; and when the request is made to /hello/jp
, it will be equal to "/hello".
req.baseUrl
is similar to the mountpath
property
of the app
object, exceptapp.mountpath
returns
the matched path pattern(s).
Response
res.status(code)
Chainable alias of node's res.statusCode
.
Use this method to set the HTTP status for the response.
res.status(403).end();
res.status(400).send('Bad Request');
res.status(404).sendFile('/absolute/path/to/404.png');
res.set(field, [value])
Set header field
to value
,
or pass an object to set multiple fields at once.
res.set('Content-Type', 'text/plain');
res.set({
'Content-Type': 'text/plain',
'Content-Length': '123',
'ETag': '12345'
})
Aliased as res.header(field, [value])
.
res.get(field)
Get the case-insensitive response header field
.
res.get('Content-Type');
// => "text/plain"
res.cookie(name, value, [options])
Set cookie name
to value
,
which may be a string or object converted to JSON. Thepath
option defaults to "/".
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
The maxAge
option is a convenience option
for setting "expires" relative to the current time in milliseconds. The following is equivalent to the previous example.
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
An object may be passed which is then serialized as JSON, which is automatically parsed by the bodyParser()
middleware.
res.cookie('cart', { items: [1,2,3] });
res.cookie('cart', { items: [1,2,3] }, { maxAge: 900000 });
Signed cookies are also supported through this method. Simply pass the signed
option.
When given res.cookie()
will use the secret passed tocookieParser(secret)
to
sign the value.
res.cookie('name', 'tobi', { signed: true });
Later you may access this value through the req.signedCookie object.
res.clearCookie(name, [options])
Clear cookie name
. The path
option
defaults to "/".
res.cookie('name', 'tobi', { path: '/admin' });
res.clearCookie('name', { path: '/admin' });
res.redirect([status], url)
Location
header, without any validation or manipulation,
except in case of back
. Browsers take the responsibility of deriving the intended URL from the current URL or the referring URL, and the URL specified in the Location
header; and redirect the user accordingly.Redirect to the given url
with optional status
code
defaulting to 302 "Found".
res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login');
Redirects can be a fully qualified URI for redirecting to a different site:
res.redirect('http://google.com');
Redirects can be relative to the root of the host name. For example, if you were onhttp://example.com/admin/post/new
,
the following redirect to /admin
would land you at http://example.com/admin
:
res.redirect('/admin');
Redirects can be relative to the current URL. A redirection of post/new
,
fromhttp://example.com/blog/admin/
(notice the trailing slash), would give youhttp://example.com/blog/admin/post/new
.
res.redirect('post/new');
Redirecting to post/new
from http://example.com/blog/admin
(no
trailing slash), will take you to http://example.com/blog/post/new
.
If you found the above behavior confusing, think of path segments as directories (have trailing slashes) and files, it will start to make sense.
Pathname relative redirects are also possible. If you were onhttp://example.com/admin/post/new
,
the following redirect would land you athttp//example.com/admin/post
:
res.redirect('..');
A back
redirection will redirect the request
back to the Referer (or Referrer), defaulting to /
when missing.
res.redirect('back');
res.location
Set the location header.
res.location('/foo/bar');
res.location('foo/bar');
res.location('http://example.com');
res.location('../login');
res.location('back');
You can use the same kind of urls
as in res.redirect()
.
For example, if your application is mounted at /blog
,
the following would set thelocation
header to /blog/admin
:
res.location('admin')
res.send([body])
Send a response.
res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('some html');
res.status(404).send('Sorry, we cannot find that!');
res.status(500).send({ error: 'something blew up' });
This method performs a myriad of useful tasks for simple non-streaming responses such as automatically assigning the Content-Length unless previously defined and providing automatic HEAD and HTTP cache freshness support.
When a Buffer
is given the Content-Type is
set to "application/octet-stream" unless previously defined as shown below:
res.set('Content-Type', 'text/html');
res.send(new Buffer('some html'));
When a String
is given the Content-Type is
set defaulted to "text/html":
res.send('some html');
When an Array
or Object
is
given Express will respond with the JSON representation:
res.send({ user: 'tobi' });
res.send([1,2,3]);
res.json([body])
Send a JSON response. This method is identical to res.send()
when
an object or array is passed. However, it may be used for explicit JSON conversion of non-objects, such as null, undefined, etc. (although these are technically not valid JSON).
res.json(null)
res.json({ user: 'tobi' })
res.status(500).json({ error: 'message' })
res.jsonp([body])
Send a JSON response with JSONP support. This method is identical to res.json()
,
except that it opts-in to JSONP callback support.
res.jsonp(null)
// => null
res.jsonp({ user: 'tobi' })
// => { "user": "tobi" }
res.status(500).jsonp({ error: 'message' })
// => { "error": "message" }
By default, the JSONP callback name is simply callback
.
However, you may alter this with the jsonp callback name setting. The following are some examples of JSONP responses using the same
code:
// ?callback=foo
res.jsonp({ user: 'tobi' })
// => foo({ "user": "tobi" })
app.set('jsonp callback name', 'cb');
// ?cb=foo
res.status(500).jsonp({ error: 'message' })
// => foo({ "error": "message" })
res.type(type)
Sets the Content-Type to the mime lookup of type
,
or when "/" is present the Content-Type is simply set to this literal value.
res.type('.html');
res.type('html');
res.type('json');
res.type('application/json');
res.type('png');
res.format(object)
Performs content-negotiation on the Accept HTTP header on the request object, when present. It uses req.accepts()
to
select a handler for the request, based on the acceptable types ordered by their quality values. If the header is not specified, the first callback is invoked. When no match is found, the server responds with 406 "Not Acceptable", or invokes the default
callback.
The Content-Type response header is set for you when a callback is selected. However, you may alter this within the callback using res.set()
or res.type()
etcetera.
The following example would respond with { "message":
"hey" }
when the Accept header field is set to "application/json" or "*/json" (however if "*/*" is given, then "hey" will be the response).
res.format({
'text/plain': function(){
res.send('hey');
},
'text/html': function(){
res.send('hey');
},
'application/json': function(){
res.send({ message: 'hey' });
},
'default': function() {
// log the request and respond with 406
res.status(406).send('Not Acceptable');
}
});
In addition to canonicalized MIME types, you may also use extension names mapped to these types for a slightly less verbose implementation:
res.format({
text: function(){
res.send('hey');
},
html: function(){
res.send('hey');
},
json: function(){
res.send({ message: 'hey' });
}
});
res.attachment([filename])
Sets the Content-Disposition header field to "attachment". If a filename
is
given, then the Content-Type will be automatically set based on the extname viares.type()
, and the Content-Disposition's
"filename=" parameter will be set.
res.attachment();
// Content-Disposition: attachment
res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png
res.sendFile(path, [options], [fn])
res.sendFile
requires Express version to be at least 4.8.0Transfer the file at the given path
. The Content-Type
response header field is automatically set based on the filename's extension.
Unless the root
option is set in the options
object, path
must be an absolute path of the file.
Options:
maxAge
sets the max-age property of the Cache-Control header in milliseconds or a string in ms format, defaults to 0.root
root directory for relative filenames.lastModified
enabled by default, sets theLast-Modified
header to the last modified date of the file on the OS. Setfalse
to disable it.headers
object of HTTP headers to serve with the file.dotfiles
option for serving dotfiles. Possible values are "allow", "deny", "ignore"; defaults to "ignore".
The callback fn(err)
is invoked when the transfer
is complete or when an error occurs. If the callback function is specified and an error occurs, the response process must be handled explicitly within the callback function by either ending the request response cycle, or passing the control to the next route.
Here is an example of using res.sendFile
with
all its arguments.
app.get('/file/:name', function (req, res, next) {
var options = {
root: __dirname + '/public/',
dotfiles: 'deny',
headers: {
'x-timestamp': Date.now(),
'x-sent': true
}
};
var fileName = req.params.name;
res.sendFile(fileName, options, function (err) {
if (err) {
console.log(err);
res.status(err.status).end();
}
else {
console.log('Sent:', fileName);
}
});
})
res.sendFile
provides fine-grained support
for file serving as illustrated in the following example:
app.get('/user/:uid/photos/:file', function(req, res){
var uid = req.params.uid
, file = req.params.file;
req.user.mayViewFilesFrom(uid, function(yes){
if (yes) {
res.sendFile('/uploads/' + uid + '/' + file);
} else {
res.status(403).send('Sorry! you cant see that.');
}
});
});
Please refer to send for additional documentation or any issues and concerns.
res.sendStatus(statusCode)
Set the response HTTP status code to statusCode
and
send its string representation as the response body.
res.sendStatus(200); // equivalent to res.status(200).send('OK')
res.sendStatus(403); // equivalent to res.status(403).send('Forbidden')
res.sendStatus(404); // equivalent to res.status(404).send('Not Found')
res.sendStatus(500); // equivalent to res.status(500).send('Internal Server Error')
If an unspported status code is specified, the HTTP status is still set to statusCode
and
the string version of the code is sent as the response body.
res.sendStatus(2000); // equivalent to res.status(2000).send('2000')
res.download(path, [filename], [fn])
Transfer the file at path
as an "attachment".
Typically, browsers will prompt the user for download. The Content-Disposition "filename=" parameter (i.e. the one that will appear in the brower dialog) is set to path
by
default. However, you may provide an override filename
.
When an error has ocurred or transfer is complete the optional callback fn
is
invoked. This method uses res.sendFile() to transfer the file.
res.download('/report-12345.pdf');
res.download('/report-12345.pdf', 'report.pdf');
res.download('/report-12345.pdf', 'report.pdf', function(err){
if (err) {
// handle error, keep in mind the response may be partially-sent
// so check res.headersSent
} else {
// decrement a download credit etc
}
});
res.links(links)
Join the given links
to populate the "Link"
response header field.
res.links({
next: 'http://api.example.com/users?page=2',
last: 'http://api.example.com/users?page=5'
});
yields:
Link: <http://api.example.com/users?page=2>; rel="next",
<http://api.example.com/users?page=5>; rel="last"
res.locals
Response local variables are scoped to the request, and therefore only available to the view(s) rendered during that request / response cycle (if any). Otherwise, this API is identical to app.locals.
This object is useful for exposing request-level information such as the request pathname, authenticated user, user settings etc.
app.use(function(req, res, next){
res.locals.user = req.user;
res.locals.authenticated = ! req.user.anonymous;
next();
});
res.render(view, [locals], callback)
Render a view
with a callback responding with
the rendered string. When an error occurs next(err)
is invoked internally. When a callback is provided
both the possible error and rendered string are passed, and no automated response is performed.
res.render('index', function(err, html){
// ...
});
res.render('user', { name: 'Tobi' }, function(err, html){
// ...
});
res.vary(field)
Adds the field to the Vary
response header,
if it is not there already.
res.vary('User-Agent').render('docs');
res.end([data], [encoding])
Inherited from node's http.ServerResponse
,
ends the response process. The only recommended use is for quickly ending the response without any data. If you need to respond with data, use Express' response methods such as res.send()
,res.json()
etc.
res.end();
res.status(404).end();
res.headersSent
Property indicating if HTTP headers has been sent for the response.
app.get('/', function (req, res) {
console.log(res.headersSent); // false
res.send('OK');
console.log(res.headersSent); // true
})
Router
Router()
A router is an isolated instance of middleware and routes. Routers can be thought of as "mini" applications only capable of performing middleware and routing. Every express application has a builtin app router.
Routers behave like middleware themselves and can be ".use()'d" by the app or in other routers.
Create a new router by using "express.Router()"
var router = express.Router([options]);
Options is an optional object to alter the behavior of the router.
caseSensitive
Enable case sensitivity, disabled by default, treating "/Foo" and "/foo" as the samestrict
Enable strict routing, by default "/foo" and "/foo/" are treated the same by the routermergeParams
Ensure thereq.params
values from the parent router are preserved. If the parent and the child have conflicting param names, the child's value take precedence. Defaults tofalse
.
The router can have middleware and http VERB routes added just like an application.
// invoked for any requests passed to this router
router.use(function(req, res, next) {
// .. some logic here .. like any other middleware
next();
});
// will handle any request that ends in /events
// depends on where the router is "use()'d"
router.get('/events', function(req, res, next) {
// ..
});
You can then use a router for a particular root url in this way separating your routes into files or even mini apps.
// only requests to /calendar/* will be sent to our "router"
app.use('/calendar', router);
router.use([path], function)
Use the given middleware function
, with optional
mount path
, defaulting to "/".
Middleware is like a plumbing pipe, requests start at the first middleware you define and work their way "down" the middleware stack processing for each path they match.
var express = require('express');
var app = express();
var router = express.Router();
// simple logger for this router's requests
// all requests to this router will first hit this middleware
router.use(function(req, res, next) {
console.log('%s %s %s', req.method, req.url, req.path);
next();
});
// this will only be invoked if the path ends in /bar
router.use('/bar', function(req, res, next) {
// ... maybe some additional /bar logging ...
next();
});
// always invoked
router.use(function(req, res, next) {
res.send('Hello World');
});
app.use('/foo', router);
app.listen(3000);
The "mount" path is stripped and is not visible to the middleware function
.
The main effect of this feature is that mounted middleware may operate without code changes regardless of its "prefix" pathname.
The order of which middleware are "defined" using router.use()
is
very important, they are invoked sequentially, thus this defines middleware precedence. For example usually a logger is the very first middleware you would use, logging every request:
var logger = require('morgan');
router.use(logger());
router.use(express.static(__dirname + '/public'));
router.use(function(req, res){
res.send('Hello');
});
Now suppose you wanted to ignore logging requests for static files, but to continue logging routes and middleware defined after logger()
,
you would simply movestatic()
above:
router.use(express.static(__dirname + '/public'));
router.use(logger());
router.use(function(req, res){
res.send('Hello');
});
Another concrete example would be serving files from multiple directories, giving precedence to "./public" over the others:
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads'));
router.param([name], callback)
Map logic to route parameters. For example, when :user
is
present in a route path you may map user loading logic to automatically provide req.user
to the route,
or perform validations on the parameter input.
Note
- Param callback functions are local to the router on which they are defined. They are not inherited by mounted apps or routers. Hence, param callbacks defined on
router
will be trigerred only by route parameters defined onrouter
routes. - A param callback will be called only once in a request-response cycle, even if the parameter is matched in multiple routes.
app.param('id', function (req, res, next, id) { console.log('CALLED ONLY ONCE'); next(); }) app.get('/user/:id', function (req, res, next) { console.log('although this matches'); next(); }); app.get('/user/:id', function (req, res) { console.log('and this matches too'); res.end(); });
The following snippet illustrates how the callback
is
much like middleware, thus supporting async operations. However, it provides the additional value of the parameter (here named as id
),
derived from the corresponding parameter in thereq.params
object. An attempt to load the user is then
performed, assigningreq.user
, otherwise passing an error to next(err)
.
It is important to realize that any route that triggered a named parameter function to run will only be run if next
was
not called with an error in the named parameter handler.
router.param('user', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
return next(err);
}
else if (!user) {
return next(new Error('failed to load user'));
}
req.user = user;
next();
});
});
// this route uses the ":user" named parameter
// which will cause the 'user' param callback to be triggered
router.get('/users/:user', function(req, res, next) {
// req.user WILL be defined here
// if there was an error, normal error handling will be triggered
// and this function will NOT execute
});
Alternatively you may pass only a callback
,
in which case you have the opportunity to alter the router.param()
API. For example the express-params defines
the following callback which allows you to restrict parameters to a given regular expression.
This example is a bit more advanced. It checks whether the second argument is a regular expression, returning the callback (which acts much like the "user" param example).
router.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
});
The method could now be used to effectively validate parameters (and optionally parse them to provide capture groups):
router.param('id', /^\d+$/);
router.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
router.param('range', /^(\w+)\.\.(\w+)?$/);
router.get('/range/:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
The router.use()
method also supports named
parameters so that your mount points for other routers can benefit from preloading using named parameters.
router.route(path)
Returns an instance of a single route which can then be used to handle HTTP verbs with optional middleware. Using router.route()
is
a recommended approach to avoiding duplicate route naming and thus typo errors.
Building on the router.param()
example from
before, we see how router.route()
allows us to easily specify the various HTTP verb handlers.
var router = express.Router();
router.param('user_id', function(req, res, next, id) {
// sample user, would actually fetch from DB, etc...
req.user = {
id: id,
name: 'TJ'
};
next();
});
router.route('/users/:user_id')
.all(function(req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware!
})
.get(function(req, res, next) {
res.json(req.user);
})
.put(function(req, res, next) {
// just an example of maybe updating the user
req.user.name = req.params.name;
// save user ... etc
res.json(req.user);
})
.post(function(req, res, next) {
next(new Error('not implemented'));
})
.delete(function(req, res, next) {
next(new Error('not implemented'));
})
This apporach re-uses the single '/users/:user_id' path and add handlers for the various HTTP verbs.
router.VERB(path, [callback...], callback)
The router.VERB()
methods provide the routing
functionality in Express, whereVERB is one of the HTTP verbs, such as router.post()
.
Multiple callbacks may be given, all are treated equally, and behave just like middleware, with the one exception that these callbacks may invoke next('route')
to
bypass the remaining route callback(s). This mechanism can be used to perform pre-conditions on a route then pass control to subsequent routes when there is no reason to proceed with the route matched.
The following snippet illustrates the most simple route definition possible. Express translates the path strings to regular expressions, used internally to match incoming requests. Query strings are not considered when peforming these matches, for example "GET /" would match the following route, as would "GET /?name=tobi".
router.get('/', function(req, res){
res.send('hello world');
});
Regular expressions may also be used, and can be useful if you have very specific restraints, for example the following would match "GET /commits/71dbb9c" as well as "GET /commits/71dbb9c..4c084f9".
router.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
var from = req.params[0];
var to = req.params[1] || 'HEAD';
res.send('commit range ' + from + '..' + to);
});
Middleware
API
An Express application is essentially a stack of middleware which are executed serially.
A middleware is a function with access to the request object (req
),
the response object (res
), and the next middleware in line in the request-response cycle of an Express
application. It is commonly denoted by a variable named next
. Each middleware has the capacity to execute
any code, make changes to the request and the reponse object, end the request-response cycle, and call the next middleware in the stack. Since middleware are execute serially, their order of inclusion is important.
If the current middleware is not ending the request-response cycle, it is important to call next()
to
pass on the control to the next middleware, else the request will be left hanging.
With an optional mount path, middleware can be loaded at the application level or at the router level. Also, a series of middleware functions can be loaded together, creating a sub-stack of middleware system at a mount point.
Application level middleware
Application level middleware are bound to an instance of express
,
using app.use()
and app.VERB()
.
var app = express();
// a middleware with no mount path; gets executed for every request to the app
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// a middleware mounted on /user/:id; will be executed for any type of HTTP request to /user/:id
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// a route and its handler function (middleware system) which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
Here is an example of loading a series of middleware at a mount point with a mount path.
// a middleware sub-stack which prints request info for any type of HTTP request to /user/:id
app.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
Route handlers, being a middleware system, makes it possible to define multiple routes for a path. In the example below, two routes are defined for GET requests to/user/:id
.
The second router will not cause any problems, however it will never get called, because the first route ends the request-response cycle.
// a middleware sub-stack which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('User Info');
});
// handler for /user/:id which prints the user id
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id);
});
If you need to skip the rest of the middleware from a router middleware stack, callnext('route')
to
pass on the control to the next route. Note: next('route')
will work only in middleware loaded using app.VERB()
or router.VERB()
.
// a middleware sub-stack which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
// if user id is 0, skip to the next route
if (req.params.id == 0) next('route');
// else pass the control to the next middleware in this stack
else next(); //
}, function (req, res, next) {
// render a regular page
res.render('regular');
});
// handler for /user/:id which renders a special page
app.get('/user/:id', function (req, res, next) {
res.render('special');
});
Router level middleware
Router level middleware work just like application level middleware except they are bound to an instance of express.Router()
.
var router = express.Router();
Router level middleware are loaded using router.use()
and router.VERB()
.
The middleware system created at the application level in the example above, can be replicated at the router level using the following code.
var app = express();
var router = express.Router();
// a middleware with no mount path, gets executed for every request to the router
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// a middleware sub-stack shows request info for any type of HTTP request to /user/:id
router.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// a middleware sub-stack which handles GET requests to /user/:id
router.get('/user/:id', function (req, res, next) {
// if user id is 0, skip to the next router
if (req.params.id == 0) next('route');
// else pass the control to the next middleware in this stack
else next(); //
}, function (req, res, next) {
// render a regular page
res.render('regular');
});
// handler for /user/:id which renders a special page
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
// mount the router on the app
app.use('/', router);
Third-party middleware
Express is a routing and middleware web framework with minimal functionality of its own. Functionality to Express apps are added via third-party middleware.
Install the node module for the required functionality and loaded it in your app at the application level or at the router level.
In the following example, cookie-parser
, a
cookie parsing middleware is installed and loaded in the app.
$ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
// load the cookie parsing middleware
app.use(cookieParser);
Here is a list of third-party middleware commonly used with Express.
Built-in middleware
As of 4.x, Express no longer depends on Connect. Except for express.static
,
all of Express' previously included middleware are now in separate repos. Please view the list of middleware.
express.static(root, [options])
express.static
is based on serve-static
,
and is responsible for serving the static assets of an Express application.
The root
argument refers to the root directory
from which the static assets are to be served.
The optional options
object can have the following
properties.
dotfiles
option for serving dotfiles. Possible values are "allow", "deny", "ignore"; defaults to "ignore".etag
enable or disable etag generation, defaults totrue
.extensions
sets file extension fallbacks, defaults tofalse
.index
sends directory index file, defaults to "index.html". Setfalse
to disable directory indexing.lastModified
enabled by default, sets theLast-Modified
header to the last modified date of the file on the OS. Setfalse
to disable it.maxAge
sets the max-age property of the Cache-Control header in milliseconds or a string in ms format, defaults to 0.redirect
redirects to trailing "/" when the pathname is a dir, defaults totrue
.setHeaders
function for setting HTTP headers to serve with the file.
Here is an example of using the express.static
middleware
with an elaborate options object.
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path) {
res.set('x-timestamp', Date.now())
}
};
app.use(express.static('public', options));
You can have more than one static directory per app.
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));
For more details about serve-static
and its
options, visit the serve-static
documentation.
相關文章
- 精講Flutter官網的第一個例子Flutter
- 全網最全,講解最細的ps日常操作小技巧,Photoshop教程
- 最全面最詳細的字符集講解來了!
- 用最簡單的例子講解js中解決非同步的方法JS非同步
- 最全最強SpringMVC詳細示例實戰SpringMVC
- 最強最全面的數倉建設規範指南
- 史上最全最強SpringMVC詳細示例實戰教程SpringMVC
- 全網最!詳!細!Tarjan演算法講解。演算法
- Android——RxJava2史上最全講解AndroidRxJava
- 2022年官網下安裝GIT最全版與官網查閱方法Git
- 2022年官網下安裝RabbitMQ最全版與官網查閱方法MQ
- 跟著官網的例子學Reacjs (一)FilterableProductTableJSFilter
- 史上最全的Rxjava2講解(使用篇)RxJava
- 最全面的Android Intent機制講解AndroidIntent
- 2022年官網下安裝MongoDB最全版與官網查閱方法(5.0.6)MongoDB
- 最全最強!世界大學計算機專業排名總結!計算機
- web前端開發教程,最全JavaScript入門講解Web前端JavaScript
- Spring Boot註解@Transactional結合實際例子講解Spring Boot
- 最強最全面的Hive SQL開發指南,超四萬字全面解析HiveSQL
- 全網最通透的Java8版本特性講解Java
- Redis 入門 - 安裝最全講解(Windows、Linux、Docker)RedisWindowsLinuxDocker
- 深入講解拉鍊表,還怕面試官問?面試
- 可能是全網最詳細的express--middlewareExpress
- 史上最清晰的「歸併排序」講解排序
- 最詳細的JVM&GC講解JVMGC
- 史上最全redux,react-redux,中介軟體機制講解ReduxReact
- JavaScript陣列詳解-全網最全JavaScript陣列
- Spark SQL 最簡單例子SparkSQL單例
- DvaJS的學習之路1 - dva+umi官網例子學習JS
- RestTemplate全網最強總結(永久更新)REST
- Spark SQL的官網解釋SparkSQL
- 深度解讀 MongoDB 最全面的增強版本 4.4 新特性MongoDB
- 一文講通.NET Core部署到Windows IIS最全解決方案Windows
- webpack4.x最詳細入門講解Web
- 全球網際網路最強木馬:Sinowal被曝光
- Express進階:從一個例子看路由分組機制Express路由
- 面試官:講講雪花演算法,越詳細越好面試演算法
- 用一個通俗的例子講清楚APIAPI