<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>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] // ## 導讀 // 我有一個問題和不太成熟的想法,不知道該不該提! // 掘金既然支持目錄TOC,為什么不能把目錄放在一個顯眼的地方,比如左邊?一大片空白不用非要放在右下角和其它面板搶,emmm... // - express概要 // - 創建一個服務器以及分發路由 // - 簡單實現1 // - 脈絡 // - 實現路由 // - .all方法和實現 // - 應用 // - 實現 // - 在app下添加.all方法(用來存儲路由信息對象) // - 對遍歷路由信息對象時的規則判斷做出調整 // - 中間件 // - 概要 // - 中間件和路由的異同 // - 1.都會包裝成路由信息對象 // - 2.匹配條數的不同 // - 3.匹配路徑上的不同 // - next與錯誤中間件 // - 實現 // - 在app下添加.use方法 // - 改造request方法 // - params // - params常用屬性 // - params與動態路由 // - 實現 // - 動態路由與動態路由屬性的實現 // - params其它屬性的實現 // - .param方法 // - api一覽 // - 注意事項 // - 應用場景 // - 和中間件的區別 // - 實現 // - 添加param方法 // - 修改request ## express概要 express是一個node模塊,它是對node中`http`模塊的二次封裝。 express相較于原生http模塊,為我們提供了作為一個服務器最重要的功能:`路由`。 路由功能能幫助我們**根據不同的路徑不同的請求方法來返回不同的內容**。 除此之外express還支持 `中間件` 以及其他類似于 `req.params` 這些小功能。 ## 創建一個服務器以及分發路由 ``` let express = require('express'); let app = express(); //針對不同的路由進行不同的返回 app.get('/eg1',function(req,res){ res.end('hello'); }); app.post('/eg1',function(req,res){ res.end('world'); }); app.listen(8080,function(){ console.log(`server started at 8080`); }); ``` 可以發現,引入express后會返回一個**函數**,我們稱之為express。 express這個函數運行后又會返回一個對象,這個對象就是包裝后的http的**server對象**。 這個對象下有很多方法,這些方法就是express框架為我們提供的新東東了。 上面用到了`.get`方法和`.post`方法,get和post方法能幫助我們對路由進行分發。 什么是路由分發呢?其實就是在原生`request`回調中依據`請求方法`和`請求路徑`的不同來返回不同的響應內容。 就像在上面的示例中我們通過`.get`和`.post`方法對路徑為`/eg1`的請求**各**綁定了一個回調函數, 但這個兩個回調函數不會同時被調用,因為請求方法只能是一種(get或則post或則其它)。 如果請求方法是**get**請求路徑是`/eg1`則會返回`.get`中所放置的回調 ``` <<< 輸出 hello ``` 否則若請求路徑不變,請求方法是**post**則會返回`.post`方法中放置的回調 ``` <<< 輸出 world ``` ### 簡單實現1 #### 脈絡 我們首先要有一個函數,這個函數運行時會返回一個app對象 ``` function createApplication(){ let app = function(req,res){}; return app; } ``` 這個app對象下還有一些方法`.get`,`.post`,`.listen`。 ``` app.get = function(){} app.post = function(){} app.listen = function(){} ``` 其中`app.listen`其實就是原生http中的`server.listen`。 `app`就是原生中的`request`回調。 ``` app.listen = function(){ let server = http.createServer(app); server.listen.apply(server,arguments); //事件回調中,不管怎樣this始終指向綁定對象,這里既是server,原生httpServer中也是如此 } ``` #### 實現路由 我們再來想想`app.get`這些方法到底做了什么。 其實無非定義了一些路由規則,對匹配上這些規則的路由進行一些針對性的處理(執行回調)。 上面一句話做了兩件事,匹配規則 和 執行回調。 這兩件事執行的時機是什么時候呢?是服務器啟動的時候嗎?不是。 **是當接收到客戶端請求的時候**。 這意味著什么? 當服務器啟動的時候,其實這些代碼已經執行了,它們根本不會管請求是個什么鬼,只要服務器啟動,代碼就執行。 所以我們需要將**規則**和**回調**先**存起來**。(類似于發布訂閱模式) ``` app.routes = []; app.get = function(path,handler){ app.routes.push({ method:'get' ,path ,handler }) } ``` 上面我們定義了一個`routes`數組,用來存放每一條規則和規則所對應的回調以及請求方式,即`路由信息對象`。 但有一個地方需要我們優化。不同的請求方法所要做的事情都是相同的(只有method這個參數不同),我們不可能每增加一個就重復的寫一次,請求的方法是有非常多的,這樣的話代碼會很冗余。 ``` //http.METHODS能列出所有的請求方法 >>> console.log(http.METHODS.length); <<< 33 ``` So,為了簡化我們的代碼我們可以遍歷`http.METHODS`來創建函數 ``` http.METHODS.forEach(method){ let method = method.toLowerCase(); app[method] = function(path,handler){ app.routes.push({ method ,path ,handler }) } } ``` 然后我們會在請求的響應回調中用到這些路由信息對象。而**響應回調在哪呢?** 上面我們已經說過其實`app`這個`函數對象`就是原生的`request`回調。 接下來我們只需要等待請求來臨然后執行這個app回調,**遍歷每一個路由信息對象進行匹配,匹配上了則執行對應的回調函數。** ``` let app = function(req,res){ for(let i=0;i<app.routes.length;++i){ let route = app.routes[i]; let {pathname} = url.parse(req.url); if(route.method==req.method&&route.path==pathname){ route.handler(req,res); } } } ``` ## .all方法和實現 ### 應用 `.all`也是一個路由方法, ``` app.all('/eg1',function(req,res){}) ``` 和普通的`.get`,`.post`這些和請求方法直接綁定的路由分發不同,.all方法只要路徑匹配得上**各種請求方法**去請求這個路由都會得到響應。 還有一種更暴力的使用方式 ``` app.all('*',function(req,res){}) ``` 這樣能匹配所有方法所有路勁,**all!** **通常它的使用場景是對那些沒有匹配上的請求做出兼容處理。** ### 實現 #### 在app下添加.all方法(用來存儲路由信息對象) 和一般的請求方法是一樣的,只是需要一個標識用以和普通方法區分開。 這里是在method取了一個`all`關鍵之作為method的值。 ``` app.all = function(method,handler){ app.routs.push({ method:'all' ,path ,handler }) } ``` #### 對遍歷路由信息對象時的規則判斷做出調整 另外還需要在`request`回調中對規則的匹配判斷做出一些調整 ``` if((route.method==req.method||route.method=='all')&&(route.path==pathname||route.path=='*')){ route.handler(req,res); } ``` ## 中間件 ### 概要 中間件是什么鬼呢?中間件嘛,顧名思義中間的件。。。emmm,我們直接說說它的作用吧! 中間件主要是**在請求和真正響應之間**再加上一層處理, 處理什么呢?比如說權限驗證、數據加工神馬的。 這里所謂的**真正響應**,你可以把它當做`.get`這些路由方法所要執行的那些個回調。 ``` app.use('/eg2',function(req,res,next){ //do something next(); }) ``` ### 中間件和路由的異同 #### 1.都會包裝成路由信息對象 服務器啟動時,中間件也會像路由那樣被存儲為一個一個`路由信息對象`。 #### 2.匹配條數的不同 `路由`只要匹配上了一條就會立馬返回數據并結束響應,不會再匹配第二條。 而`中間件`只是一個臨時中轉站,對數據進行過濾或則加工后會繼續往下匹配。 **So,中間件一般放在文件的上方,路由放在下方。** #### 3.匹配路徑上的不同 中間件進行路徑匹配時,只要**開頭**匹配的上就能執行對應的回調。 這里所謂的開頭意思是: 假若中間件要匹配的路徑是`/eg2`, 那么只要url.path是以`/eg2`開頭,像`/eg2`,`/eg2/a`,`/eg2/a/b`即可。(/eg2a這種不行,且必須以/eg2開頭,a/eg2則不行) 而路由匹配路徑時必須是完全匹配,也就是說規則若是`/eg2`則只有`/eg2`匹配的上。這里的完全匹配其實是針對路徑的 **/的數量** 來說的,因為`動態路由`中匹配的值不是定死的。 除此之外,中間件可以不寫路徑,當不寫路徑時express系統會為其默認填上`/`,即全部匹配。 ### next與錯誤中間件 中間件的回調相較于路由多了一個參數`next`,next是一個函數。 這個函數能讓中間件的回調執行完后繼續向下匹配,如果**沒有寫next也沒有在中間件中結束響應**,那么請求會一直處于`pending`狀態。 next還可以進行傳參,如果傳了慘,表示程序運行出錯,將匹配`錯誤中間件`進行處理**且只會交由錯誤中間件處理**。 錯誤中間件相較于普通中間件在回調函數中又多了一個參數`err`,用以接收中間件`next()`傳遞過來的錯誤信息。 ``` app.use('/eg2',function(req,res,next){ //something wrong next('something wrong!'); }) app.use('/eg2',function(err,req,res,next){ console.log('i catch u'+err); next(err); //pass to another ErrorMiddle }); // 錯誤中間接收了錯誤信息后仍然允許接著向下傳遞 app.use('/eg2',function(err,req,res,next){ res.end(err); }); ``` 其實錯誤中間件處理完成后也能匹配路由 ``` app.use('/eg2',function(req,res,next){ //something wrong next('something wrong!'); }) app.use('/eg2',function(err,req,res,next){ console.log('i catch u'+err); next(err); //pass to another ErrorMiddle }); app.get('/eg2',function(req,res){ //do someting }) ``` ### 實現 #### 在app下添加.use方法 像路由方法一樣,其實就是用來存儲路由信息對象 ``` app.use = function(path,handler){ if(typeof handler != 'function'){ //說明只有一個參數,沒有path只有handler handler = path; path = "/" } app.routes.push({ method:'middle' //需要一個標識來區分中間件 ,path ,handler }); }; ``` #### 改造request方法 ``` let app = function(req,res){ const {pathname} = url.parse(req.url, true); let i = 0; function next(err){ if(index>=app.routes.length){ //說明路由信息對象遍歷完了仍沒匹配上,給出提示 return res.end(`Cannot ${req.method} ${pathname}`); } let route = app.routes[i++]; if(err){ //是匹配錯誤處理中間件 //先判斷是不是中間件 if(route.method == 'middle'){ //如果是中間件再看路徑是否匹配 if(route.path=='/'||pathname.startsWith(route.path+'/')||route.path==pathname){ //再看是否是錯誤處理中間件 if(route.handler.length==4){ route.handler(err,req,res,next); }else{ next(err); } }else{ next(err); } }else{ next(err); //將err向后傳遞直到找到錯誤處理中間件 } }else{ //匹配路由和普通中間件 if(route.method == 'middle'){ //說明是中間件 if(route.path=='/'||pathname.startsWith(route.path+'/')||route.path==pathname){ route.handler(req,res,next); }else{ //此條路由沒有匹配上,繼續向下匹配 next(); } }else{ //說明是路由 if((route.method==req.method||route.method=='all')&&(route.path==pathname||route.path=='*')){ //說明匹配上了 route.handler(req,res); }else{ next(); } } } } next(); } ``` 我們可以把對錯誤中間件的判斷封裝成一個函數 ``` function checkErrorMiddleware(route){ if(route.method == 'middle'&&(route.path=='/'||pathname.startsWith(route.path+'/')||route.path==pathname)&&route.handler.length==4){ return true; }else{ next(err); } } ``` ## params ### params常用屬性 express為我們在`request`回調中的req對象參數下封裝了一些常用的屬性 ``` app.get('/eg3',function(req,res){ console.log(req.hostname); console.log(req.query); console.log(req.path); }) ``` ### params與動態路由 ``` app.get('/article/:artid',function(req,res){ console.log(req.artid); }) >>> /article/8 <<< 8 ``` ### 實現 #### 動態路由與動態路由屬性的實現 首先因為路由規則所對應的路徑我們看得懂,但機器看不懂。 So我們需要在存儲路由信息對象時,對路由的規則進行**正則提煉**,將其轉換成正則的規則。 ``` ... app[method] = function(path,handler){ let paramsNames = []; path = path.replace(/:([^\/]+)/g,function(/*/:aaa ,aaa*/){ paramsNames.push(arguments[1]); //aaa return '([^\/]+)'; // /user/:aaa/:bbb 被提煉成 /user/([^\/]+)/([^\/]+) }); layer.reg_path = new RegExp(path); layer.paramsNames = paramsNames; } app.routes.push(layer); } ``` 我們拿到了一個`paramsNames`包含所有路徑的分塊,并將每個分塊的值作為了一個新的param的名稱, 我們還拿到了一個`reg_path`,它能幫助我們對請求的路徑進行分塊匹配,匹配上的每一個子項就是我們新param的值。 對`request`路由匹配部分做出修改 ``` if(route.paramsNames){ let matchers = pathname.match(req.reg_path); if(matchers){ let params = {}; for(let i=0;i<route.paramsNames.length;++i){ params[route.paramsNames[i]] = matchers[i+1]; //marchers從第二項開始才是匹配上的子項 } req.params = params; } route.handler(req,res); } ``` #### params其它屬性的實現 這里是內置中間件,即在框架內部,它會在第一時間被注冊為路由信息對象。 實現很簡單,就是利用`url`模塊對`req.url`進行解析 ``` app.use(function(req,res,next){ const urlObj = url.parse(req.url,true); req.query = urlObj.query; req.path = urlObj.pathname; req.hostname = req.headers['host'].split(':')[0]; next(); }); ``` ## .param方法 ### api一覽 ``` app.param('userid',function(req,res,next,id){ req.user = getUser(id); next(); }); ``` next和中間件那個不是一個意思,這個next執行的話會執行被匹配上的那條動態路由所對應的回調 id為請求時userid這個路徑位置的實際值,比如 ``` 訪問路徑為:http://localhost/ahhh/9 動態路由規則為:/username/userid userid即為9 ``` ### 注意事項 **必須配合動態路由!!** param和其它方法最大的一點不同在于,**它能對路徑進行截取匹配**。 什么意思呢, 上面我們講過,路由方法路徑匹配時必須**完全匹配**,而中間件路徑匹配時需要**開頭一樣**。 而`param`方法**無需開頭一樣,也無需完全匹配**,它只需要路徑中某一個**分塊**(即用`/`分隔開的每個路徑分塊)和方法的規則對上即可。 ### 應用場景 當不同的路由中包含相同路徑分塊且使用了相同的操作時,我們就可以對這部分代碼進行提取優化。 比如每個路由中都需要根據id獲取用戶信息 ``` app.get('/username/:userid/:name',function(req,res){} app.get('/userage/:userid/:age',function(req,res){} app.param('userid',function(req,res,next,id){ req.user = getUser(id); next(); }); ``` ### 和中間件的區別 相較于中間件它更像是一個**真正的鉤子**,它不存在放置的先后問題。 如果是中間件,一般來說它必須放在文件的上方,而param方法不是。 導致這樣結果的本質原因在于,**中間件類似于一個路由**,它會在請求來臨時加入的路由匹配隊列中參與匹配。而**param并不會包裝成一個路由信息對象**也就不會參與到隊列中進行匹配, 它的觸發時機是在**它所對應的那些動態路由被匹配上時**才會觸發。 ### 實現 #### 添加param方法 在app下添加了一個param方法,并且創建了一個`paramHandlers`對象來存儲這個方法所對應的回調。 ``` app.paramHandlers = {}; app.param = function(name,handler){ app.paramHandlers[name] = handler; //userid }; ``` #### 修改request 修改`request`回調中 動態路由被匹配上時的部分 當動態路由被匹配上時,通過它的動態路由參數來遍歷`paramHandlers`,看是否設置了對應的`param回調`, ``` if(route.paramsNames){ let matchers = pathname.match(route.reg_path); if(matchers){ let params = {}; for(let i=0;i<route.paramsNames.length;++i){ params[route.paramsNames[i]] = matchers[i+1]; } req.params = params; for(let j=0;j<route.paramsNames.length;++j){ let name = route.paramsNames[j]; let handler = app.paramHandlers[name]; if(handler){ //回調觸發更改在了這里 //第三個參數為next,這里把route.handler放在了這里,是讓param先執行再執行該條路由 return handler(req,res,()=>route.handler(req,res),req.params[name]); }else{ return route.handler(req,res); } } }else{ next(); } } ``` --- ## 源碼 ``` let http = require('http'); let url = require('url'); function createApplication() { //app其實就是真正的請求監聽函數 let app = function (req, res) { const {pathname} = url.parse(req.url, true); let index = 0; function next(err){ if(index>=app.routes.length){ return res.end(`Cannot ${req.method} ${pathname}`); } let route = app.routes[index++]; if(err){ //先判斷是不是中間件 if(route.method == 'middle'){ //如果是中間件再看路徑是否匹配 if(route.path=='/'||pathname.startsWith(route.path+'/')||route.path==pathname){ //再看是否是錯誤處理中間件 if(route.handler.length==4){ route.handler(err,req,res,next); }else{ next(err); } }else{ next(err); } }else{ next(err); //將err向后傳遞直到找到錯誤處理中間件 } }else{ if(route.method == 'middle'){ //中間件 //只要請求路徑是以此中間件的路徑開頭即可 if(route.path=='/'||pathname.startsWith(route.path+'/')||route.path==pathname){ route.handler(req,res,next); }else{ next(); } }else{ //路由 if(route.paramsNames){ let matchers = pathname.match(route.reg_path); if(matchers){ let params = {}; for(let i=0;i<route.paramsNames.length;++i){ params[route.paramsNames[i]] = matchers[i+1]; } req.params = params; for(let j=0;j<route.paramsNames.length;++j){ let name = route.paramsNames[j]; let handler = app.paramHandlers[name]; if(handler){ //如果存在paramHandlers 先執行paramHandler再執行路由的回調 return handler(req,res,()=>route.handler(req,res),req.params[name]); }else{ return route.handler(req,res); } } }else{ next(); } }else{ if ((route.method == req.method.toLowerCase() || route.method == 'all') && (route.path == pathname || route.path == '*')) { return route.handler(req, res); }else{ next(); } } } } } next(); }; app.listen = function () { //這個參數不一定 let server = http.createServer(app); //server.listen作為代理,將可變參數透傳給它 server.listen.apply(server, arguments); }; app.paramHandlers = {}; app.param = function(name,handler){ app.paramHandlers[name] = handler; //userid }; //此數組用來保存路由規則 app.routes = []; // console.log(http.METHODS); http.METHODS.forEach(function (method) { method = method.toLowerCase(); app[method] = function (path, handler) { //向數組里放置路由對象 const layer = {method, path, handler}; if(path.includes(':')){ let paramsNames = []; //1.把原來的路徑轉成正則表達式 //2.提取出變量名 path = path.replace(/:([^\/]+)/g,function(){ //:name,name paramsNames.push(arguments[1]); return '([^\/]+)'; }); // /user/ahhh/12 // /user/([^\/]+)/([^\/]+) layer.reg_path = new RegExp(path); layer.paramsNames = paramsNames; } app.routes.push(layer); }; }); //all方法可以匹配所有HTTP請求方法 app.all = function (path, handler) { app.routes.push({ method: 'all' , path , handler }); }; //添加一個中間件 app.use = function(path,handler){ if(typeof handler != 'function'){ //說明只有一個參數,沒有path只有handler handler = path; path = "/" } app.routes.push({ method:'middle' //需要一個標識來區分中間件 ,path ,handler }); }; //系統內置中間件,用來為請求和響應對象添加一些方法和屬性 app.use(function(req,res,next){ const urlObj = url.parse(req.url,true); req.query = urlObj.query; req.path = urlObj.pathname; req.hostname = req.headers['host'].split(':')[0]; next(); }); return app; } module.exports = createApplication; ```
                  <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>

                              哎呀哎呀视频在线观看