Created
July 18, 2019 03:37
-
-
Save GivenCui/5d4c53bd1860af0c2be3ded6b2d04d3f to your computer and use it in GitHub Desktop.
connect v3.7.0的源码分析记录
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* connect | |
* Copyright(c) 2010 Sencha Inc. | |
* Copyright(c) 2011 TJ Holowaychuk | |
* Copyright(c) 2015 Douglas Christopher Wilson | |
* MIT Licensed | |
*/ | |
'use strict'; | |
/** | |
* Module dependencies. | |
* @private | |
*/ | |
var debug = require('debug')('connect:dispatcher'); | |
var EventEmitter = require('events').EventEmitter; | |
var finalhandler = require('finalhandler'); | |
var http = require('http'); | |
var merge = require('utils-merge'); | |
var parseUrl = require('parseurl'); | |
/** | |
* Module exports. | |
* @public | |
*/ | |
module.exports = createServer; | |
/** | |
* Module variables. | |
* @private | |
*/ | |
var env = process.env.NODE_ENV || 'development'; | |
var proto = {}; | |
/* istanbul ignore next */ | |
var defer = typeof setImmediate === 'function' | |
? setImmediate | |
: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) } | |
/** | |
* Create a new connect server. | |
* | |
* @return {function} | |
* @public | |
*/ | |
function createServer() { | |
function app(req, res, next){ app.handle(req, res, next); } | |
merge(app, proto); | |
merge(app, EventEmitter.prototype); | |
app.route = '/'; | |
app.stack = []; | |
return app; | |
} | |
/** | |
* Utilize the given middleware `handle` to the given `route`, | |
* defaulting to _/_. This "route" is the mount-point for the | |
* middleware, when given a value other than _/_ the middleware | |
* is only effective when that segment is present in the request's | |
* pathname. | |
* | |
* For example if we were to mount a function at _/admin_, it would | |
* be invoked on _/admin_, and _/admin/settings_, however it would | |
* not be invoked for _/_, or _/posts_. | |
* | |
* @param {String|Function|Server} route, callback or server | |
* @param {Function|Server} callback or server | |
* @return {Server} for chaining | |
* @public | |
*/ | |
proto.use = function use(route, fn) { | |
var handle = fn; | |
var path = route; | |
// default route to '/' | |
if (typeof route !== 'string') { | |
handle = route; | |
path = '/'; | |
} | |
// wrap sub-apps | |
if (typeof handle.handle === 'function') { | |
var server = handle; | |
server.route = path; | |
handle = function (req, res, next) { | |
server.handle(req, res, next); | |
}; | |
} | |
// wrap vanilla http.Servers | |
if (handle instanceof http.Server) { | |
handle = handle.listeners('request')[0]; | |
} | |
// strip trailing slash | |
if (path[path.length - 1] === '/') { | |
path = path.slice(0, -1); | |
} | |
// add the middleware | |
debug('use %s %s', path || '/', handle.name || 'anonymous'); | |
this.stack.push({ route: path, handle: handle }); | |
return this; | |
}; | |
/** | |
* Handle server requests, punting them down | |
* the middleware stack. | |
* | |
* @private | |
*/ | |
proto.handle = function handle(req, res, out) { | |
var index = 0; | |
var protohost = getProtohost(req.url) || ''; | |
var removed = ''; | |
var slashAdded = false; | |
var stack = this.stack; | |
// final function handler | |
var done = out || finalhandler(req, res, { | |
env: env, | |
onerror: logerror | |
}); | |
// store the original URL | |
req.originalUrl = req.originalUrl || req.url; | |
function next(err) { | |
if (slashAdded) { | |
req.url = req.url.substr(1); | |
slashAdded = false; | |
} | |
if (removed.length !== 0) { | |
req.url = protohost + removed + req.url.substr(protohost.length); | |
removed = ''; | |
} | |
// next callback | |
var layer = stack[index++]; | |
// all done | |
if (!layer) { | |
defer(done, err); | |
return; | |
} | |
// route data | |
var path = parseUrl(req).pathname || '/'; | |
var route = layer.route; | |
// skip this layer if the route doesn't match | |
if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) { | |
return next(err); | |
} | |
// skip if route match does not border "/", ".", or end | |
var c = path.length > route.length && path[route.length]; | |
if (c && c !== '/' && c !== '.') { | |
return next(err); | |
} | |
// trim off the part of the url that matches the route | |
if (route.length !== 0 && route !== '/') { | |
removed = route; | |
req.url = protohost + req.url.substr(protohost.length + removed.length); | |
// ensure leading slash | |
if (!protohost && req.url[0] !== '/') { | |
req.url = '/' + req.url; | |
slashAdded = true; | |
} | |
} | |
// call the layer handle | |
call(layer.handle, route, err, req, res, next); | |
} | |
next(); | |
}; | |
/** | |
* Listen for connections. | |
* | |
* This method takes the same arguments | |
* as node's `http.Server#listen()`. | |
* | |
* HTTP and HTTPS: | |
* | |
* If you run your application both as HTTP | |
* and HTTPS you may wrap them individually, | |
* since your Connect "server" is really just | |
* a JavaScript `Function`. | |
* | |
* var connect = require('connect') | |
* , http = require('http') | |
* , https = require('https'); | |
* | |
* var app = connect(); | |
* | |
* http.createServer(app).listen(80); | |
* https.createServer(options, app).listen(443); | |
* | |
* @return {http.Server} | |
* @api public | |
*/ | |
proto.listen = function listen() { | |
var server = http.createServer(this); | |
return server.listen.apply(server, arguments); | |
}; | |
/** | |
* Invoke a route handle. | |
* @private | |
*/ | |
function call(handle, route, err, req, res, next) { | |
var arity = handle.length; | |
var error = err; | |
var hasError = Boolean(err); | |
debug('%s %s : %s', handle.name || '<anonymous>', route, req.originalUrl); | |
try { | |
if (hasError && arity === 4) { | |
// error-handling middleware | |
handle(err, req, res, next); | |
return; | |
} else if (!hasError && arity < 4) { | |
// request-handling middleware | |
handle(req, res, next); | |
return; | |
} | |
} catch (e) { | |
// replace the error | |
error = e; | |
} | |
// continue | |
next(error); | |
} | |
/** | |
* Log error using console.error. | |
* | |
* @param {Error} err | |
* @private | |
*/ | |
function logerror(err) { | |
if (env !== 'test') console.error(err.stack || err.toString()); | |
} | |
/** | |
* Get get protocol + host for a URL. | |
* | |
* @param {string} url | |
* @private | |
*/ | |
function getProtohost(url) { | |
if (url.length === 0 || url[0] === '/') { | |
return undefined; | |
} | |
var fqdnIndex = url.indexOf('://') | |
return fqdnIndex !== -1 && url.lastIndexOf('?', fqdnIndex) === -1 | |
? url.substr(0, url.indexOf('/', 3 + fqdnIndex)) | |
: undefined; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Mark一下, 本地验证过后, 再同步更改... (又立了一个flag😁)