<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] ## Pre-Notify 閱讀本文前可以先參考一下我之前那篇簡單版的express實現的文章。 > []() 相較于之前那版,此次我們將實現Express所有核心功能。 (づ ̄ 3 ̄)づ Let's Go! ## 框架目錄 ``` express/ | | | - application.js #app對象 | | - html.js #模板引擎 | | - route/ | | - index.js #路由系統(router)入口 | | - route.js #路由對象 | | - layer.js #router/route層 | | - middle/ | | - init.js #內置中間件 | ·- express.js #框架入口 ``` ## express.js 和 application.js 之前我們說過,將express引入到項目后會返回一個函數,當這個函數運行后會返回一個`app`對象,這個app對象是原生http的超集。 我們先來實現這個生產app對象的函數 ``` // express.js cont Application = require('./application.js'); function createApplication(){ return new Application; } module.exports = createApplication; ``` 我們在`express.js`中引入了一個`application.js` 文件,這個application文件導出的即是我們的`app對象`。 ## app對象之http服務器 `app對象` 的主要作用是用來啟動一個`http服務器`,通過`.listen`方法。 ``` const http = require('http'); function Application(){ } Application.prototype.listen = function(){ let server = http.createServer(function(req,res){ //作出響應 }) server.listen.apply(server,arguments); } ``` ## app對象之路由功能 `app對象`的另外一個重要作用,也就是`Express`框架的主要作用是實現路由功能。 路由功能,即讓服務器針對客戶端不同的請求路徑和請求方法做出不同的回應。 而要實現這個功能我們需要做兩件事情:`注冊路由` 和 `路由分發` >[warning] app對象中其實只包含對外的接口,真正實現部分是委托給路由系統(router.js)來處理的。 ### 注冊路由 當一個請求來臨時,我們可以依據它的請求方式和請求路徑來決定服務器**是否給予響應以及怎么響應。** 而我們怎么讓服務器知道哪些請求該給予響應以及怎樣響應呢? 這就是注冊路由所要做的事情了。 在服務器啟動時,我們需要對服務器想要給予回應的請求做上記錄,**先存起來**,這樣在請求來臨的時候服務器就能對照這些記錄分別作出響應。 >[warning]注意 每一條記錄都對應一條請求,記錄中一般都包含著這條請求的請求路徑和請求方式。但一條請求不一定只對應一條記錄(中間件、all方法什么的)。 #### 接口實現 我們通過在 app對象 上掛載`.get`、`.post`這一類的方法來實現路由的注冊。 其中`.get`方法能匹配請求方式為**get**的請求,`.post`方法能匹配請求方式為**post**的請求。 請求方式一共有33種,每一種都對應一個app下的方法,emmm...我們不可能寫33遍吧?So我們需要利用一個`methods包`來幫助我們減少代碼的冗余。 ``` const methods = require('methods'); // 這個包是http.METHODS的封裝,區別在于原生的方法名全文大寫,后者全為小寫。 methods.forEach(method){ Application.prototype[method] = function(){ //記錄路由信息到路由系統(router) this._router[method].apply(this._router,slice.call(arguments)); return this; } } //以上代碼是以下的簡寫 Application.prototype.get = fn Application.prototype.post = fn ... ``` 因為`app`這個對象只是作為一個`對外接口層`,為了保證此接口層一目明了,我們把實際累死累活的工作外包給其它專門的模塊來處理,比如說注冊路由,我們交給了`router`這個模塊來處理。 ### 分發路由 當請求來臨時我們就需要依據記錄的路由信息來作出對應的響應了,這個過程我們稱之為`分發路由/dispatch` 上面是廣義的分發路由的含義,但其實分發路由其實包括兩個過程,`匹配路由` 和 `分發路由`。 當一個請求來臨時,我們需要知道我們所記錄的路由信息中是否囊括這條請求。如果沒有囊括,一般來說服務器會對客戶端作出一個提示性的回應,這就是 **路由的匹配**。 如果囊括,則會執行被匹配上的路由信息中所存儲的回調(即上面所述服務器應該怎樣給予客戶端響應)。這就是**路由的分發**。 #### 接口實現 ``` Application.prototype.listen = function(){ let self = this; let server = http.createServer(function(req,res){ function done(){ //沒有匹配上路由時的回調 res.end(`Cannot ${req.method} ${req.url}`); } //將路由匹配的具體處理交給路由系統的handle方法 //handle方法中會對匹配上的路由再進行路由分發 self._router.handle(req,res,done); }) server.listen.apply(server,arguments); } ``` ### 路由懶加載 服務器啟動時并不一定會加載路由系統,只有當我們調用路由注冊方法注冊路由時才會加載router。 ``` Application.prototype.lazyrouter = function(){ if(!this._router){ this._router = new Router(); } }; ``` ``` ... self.lazyrouter(); self._router.handle(req,res,done); ... ``` ## router路由系統 ``` function Router(){ function router(req,res,next){ //為支持子路由容器功能做準備 router.handle(req,res,next); } Object.setPrototypeOf(router,proto); router.stack = []; router.paramCallbacks = {}; router.use(init); // 加載內置中間件 return router; } let proto = Object.create(null); proto.route = function(path){} //添加一層路由 methods.forEach(function(method){ //33個注冊路由的普通方法:get、post }) proto.use = function(path,handler){} //注冊中間件 proto.param = function(name,handler){} //注冊param回調 proto.handle = function(req,res,out){} //路由匹配函數 包括路由分發 proto.process_params = function(){} //處理param回調的handle ``` ### router、route和layer 當我們調用app.get('/user',cb1,cb2...)方法的時候,其實我們調用的是router的get方法。 在這個方法里我們將`路由信息`抽象成了一個對象——route,并往router里的`stack`存放了一層,同時我們也在route里開辟了一個`stack`,我們也往route里的stack里存放了一層。 ``` methods.forEach(function(method){ proto[method] = function(path){ let route = this.route(path); //注冊路由,往Router里添加一層 route[method].apply(route,slice.call(arguments,1)); //向Route里添加一層 return this; } }); ``` route是routerの`stack/棧`中所存放的一層路由,實際中我們是將route又包裝成了一個`layer`對象存儲在routerのstack中。 ``` proto.route = function(path){ let route = new Route(path) ,layer = new Layer(path,route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; }; ``` 上面我們說到`route`其實也擁有自己的`stack`,這個stack里存放的是也是一層層`layer`對象。 ``` methods.forEach(function(method){ Route.prototype[method] = function(){ let handlers = slice.call(arguments); this.methods[method] = true; for(let i=0;i<handlers.length;++i){ let layer = new Layer('/',handlers[i]); layer.method = method; this.stack.push(layer); } return this; } }); ``` 綜上,我們可以發現layer是一個相對的感念,我們可以將router中每一層稱作一個`layer`,也可以將route里的每一層稱作一個`layer`。 這兩個layer的不同之處在于前者在layer的handler屬性下掛載是`route.dispatch`這個方法,而后者掛載的是我們使用注冊路由方法時所注冊的那些個**回調**。 也就是說**route里的每一層存放的才是我們真正的響應函數**,而router里每一層存放的`handle`是幫助我們路由匹配成功后對路由進行分發的。簡而言之,后者存儲的`dispatch方法`會調用前者所存儲的每個**響應回調**。 ### handle和dispatch #### 遍歷二維數據 `router.proto.handle方法` 主要對請求進行路由匹配 ``` proto.handle = function(req,res,out){ let index = 0,self = this; let {pathname} = url.parse(req.url,true); function next(err){ if(index>=self.stack.length){ return out; //路由信息清單(stack)遍歷完仍沒匹配上 } let layer = self.stack[index++]; if(layer.match(pathname)){ if(layer.route){ //說明匹配的是路由 if(layer.route.handler_method(req.method)){ layer.handle_request(req,res,next); }else{ next(err); //路徑匹配但方法名不匹配 } }else{ //說明匹配的是中間件 } }else{ next(err); //路徑不匹配 } } next(); } ``` `route.prototype.dispatch` 方法,當路由匹配上時,會執行當初該條路由注冊時所注冊的回調函數(請求方法想匹配的回調函數)。 ``` Route.prototype.dispatch = function(req,res,out){ let idx = 0,self = this; function next(err){ if(err){ //如果一旦在路由函數中出錯了,則會跳過當前路由的所有方法匹配 return out(err); } if(idx >= self.stack.length){ return out(); //此out接收的是Router里的next,即跳轉到下一條路由進行匹配 } let layer = self.stack[idx++]; if(layer.method == req.method.toLowerCase()){ layer.handle_request(req,res,next); }else{ next(); } } next(); }; ``` >[warning] 上面兩個方法中的layer的層級是不一樣的,通過它倆的配合使用達到了了對二維數據的遍歷。 #### handle_method快速匹配 我們從上面的源碼中可以發現,其實我們對一個路由的請求方法進行了兩次匹配,為什么要這樣做呢? 第一次我們是在對route進行匹配,route里有一個屬性`methods`,這個屬性告訴我們這個route下存放的響應回調都是針對哪些請求方法的。這樣,我們就只用通過查看這個`methods`屬性就能知道route中存放的響應回調支持哪些類型的請求方式,當請求方法不匹配時我們就能直接跳過當前路由而不需要再次進入route對routeのstack的每一層進行匹配,So達到了一種**快速匹配**的效果,這就是`handle_method`方法的作用。 ``` Route.prototype.handle_method = function(method){ method =method.toLowerCase(); return this.methods[method]; }; ``` ### app.route方法 用法示例: ``` app .route('/user') .get(function(req,res){ res.end('get'); }) .post(function(req,res){ res.end('post'); }) .put(function(req,res){ res.end('put'); }) .delete(function(req,res){ res.end('delete'); }) ``` 要實現上面這個方法,很簡單,只需將route這一路由層作為`.route`方法的返回值即可 ``` Application.prototype.route = function(path){ this.lazyrouter(); return this._router.route(path); }; ``` 這樣我們通過`.route`后再使用`.get`、`.post`等,就是直接調用的`route`里的`.get`和`.post`方法,**是往route里添加層**。 ## 路由容器 ``` const user = express.Router(); //router user.use(function(req,res,next){ console.log('我是一個中間件') next(); }); //在子路徑里的路徑是相對于父路徑的 //下面是指/user/2 user.get('/2',function(req,res,next){ res.end('2'); }); //use表示使用中間件,只需要匹配前綴即可 app.use('/user',user); ``` Router就是一個路由容器,而上面這個栗子其實是在Router.stack里的一層route里添加了一個新的`router`。 這是什么意思呢? 當原本的Router.stack里的一層route被匹配時,若這個route存儲的是一個新的路由容器,那么會對這個新的路由容器里的路由再次進行匹配。并且這個新的路由容器里的路由的路徑都是相當于它們的父級的。 像上面的栗子,假若請求路徑為`/user/1`,那么子路由系統的中間件會被匹配上但.get路由不會被匹配上,如果請求路徑為`/user/2`,那么都會匹配上。 ### express.Router 修改express.js文件 ``` //添加 const Router = require('./router); createApplication.Router = Router; ``` So這個路由容器就是我們原本的`router`,不過是一個全新的實例。 ### Router和router 修改 `Router function` ``` function Router(){ function router(req,res,next){ //為支持子路由容器功能做準備 router.handle(req,res,next); } Object.setPrototypeOf(router,proto); router.stack = []; router.paramCallbacks = {}; router.use(init); // 加載內置中間件 return router; } ``` 我們將原本的Router構造函數改造成了一般的函數,不論我們使用`new`方式(調用.get(path,fn)來注冊路由) 還是`Router()`方式(調用.get(path,router))我們都會得到`router`這個函數。 **注意**,`router`函數將會在路由匹配方法(router.prototype.handle)路由匹配時被執行,它執行時會再次調用`router.handle`,即遞歸調用遍歷這個新的router容器內的路由。 ``` ... let layer = self.stack[idx++]; if(layer.method == req.method.toLowerCase()){ layer.handle_request(req,res,next); ... ``` 這時有一個問題,子路由的路徑是省略了父路由的路徑的,為了能容路徑匹配正確,我們需要將傳遞給遞歸的那個`router.handle`的`req`的`url`稍作修改 ``` ... if(layer.match(pathname)){ if(!layer.route){ //這一層是中間件層 /usr/2 removed = layer.path; // /user req.url = req.url.slice(removed.length); // /2 //從這里以后開始獲取url會是不準確的,若在回調中用到了req.url需要注意 if(err){ layer.handle_error(err,req,res,next) ... ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看