【免費公開課】手把手教你實現Node.js Express框架

手把手教你實現Node.js Express框架

剛接觸 js 的同學在學到 ajax 的時候經常會懵掉,歸根結底就是對所謂的“后臺”、“服務器”這些概念沒有任何概念。課程中我講過 Express 做后臺,甚至寫了個簡單易用的 mock 工具 server-mock 來方便同學模擬數據,但經常會出現類似下面的對話:

同學:“你推薦的框架和工具我用了,用的也很爽,可是框架工具的外衣下到底發生了什么?除了 mock 數據,我還想做 HTTP 的緩存控制的測試、想做白屏和 FOUC的效果重現測試、想做靜態資源加載順序的測試、想做跨域的測試... ,如果我不明白里面后臺到底發生了什么還不如叫我去死...”

我:"多用多練,學到后面你自然就懂了,不甘心你可以先看看 Express 的源碼"

同學:“我用都還沒用熟練... 殺了我吧...”

如果想追根溯源,看源碼真的是唯一途徑,無奈源碼實在是太枯燥,為了功能的完善流行的框架引入太多和主線流程不先關的東西。即使偶爾能找到一些不錯的源碼解析的文章,也是又臭又硬,完全不適合缺少經驗的初學者。所以之前答應同學近期安排一次好懂有用的直播公開課,專門講解服務器和后端框架,盡量讓不管是前端小白還是前端老鳥都有收獲。

直播內容

本次直播課涉及的內容如下:

  • step0. 我們先使用 Nodejs 的入門知識搭建一個服務器
  • step1. 對搭建的服務器功能進行擴展,使之成為一個能用的靜態服務器
  • step2. 繼續擴展,讓我們的靜態服務器能解析路由,把服務器變成一個支持靜態目錄和動態路由的“網站”
  • step3. 模擬 Node.js 的后端框架 Express的使用方法,實現一個包含靜態目錄設置、中間件處理、路由匹配的迷你 Express 框架
  • step4. 完善這個框架

適合的對象

不需要你有萬行代碼的編寫經驗、不需要你精通/掌握/熟悉 Node.js,你只需要

  • 有一些 js 使用經驗,有一點 nodejs的使用經驗,即可理解1、2、3對應的內容。
  • 如果你有一點后端基礎,有一點Express 框架的使用經驗,那么你就能理解4、5對應的內容。

我能學到什么

  • 你會對服務器、對后端框架有一個清晰的認識
  • 平時搭個靜態服務器或者 Mock 數據不再需要使用別人的東西
  • 會對 HTTP 有更深入的認識
  • 中間件、異步有一定認識
  • 裝 13利器,以后簡歷里可以寫自己不使用任何第三方模塊,實現一個類 Express 的后端框架

課程安排

直播時間: 本周三(7月5日)晚上8點30

**參與方式:加 QQ 群:617043164 ,入群申請:簡書Node框架。

也可以在微信搜索小程序『饑人谷』,上課的時候如果不方便看電腦可在手機微信小程序上觀看直播

關于我

我是若愚,曾經在百度、阿里巴巴做前端開發,后創業加入饑人谷做前端老師。親手培養了近300名同學

一些同學去了Facebook、大眾點評、美團、頭條、金山、百度、阿里(外包)、華為(外包)
一些同學在小公司當技術 leader
一些同學去國外做菠菜網站拿著讓人"震驚"的待遇
一些同學自己做外包當老板收入甚至遠超老師
當然還有一些同學,令人悲傷的中途退課轉了行

他們來自五湖四海各行各業,海龜、名校高材生、火車司機、航海員、旅游軟件銷售、全職媽媽、裝配廠工人、方便面制造師、中科院研究所員工、美工、產品、后端、機械/化工/傳媒/英語/新聞從業者....,但最終殊(誤)途(入)同(歧)歸(途)

直播劇透

以下是公開課課堂上涉及的代碼,有興趣參加公開課的同學可以提前閱讀、理解、復制、允許代碼,Github 的鏈接直播課堂上放出。課堂上會進行一步步講解,任何疑問都可在直播課和老師直接互動。

step0: 創建一個服務器

index.js

var http = require('http')

var server = http.createServer(function(request, response){
  //response.setHeader('Content-Type', 'text/html')
  //response.setHeader('X-Powered-By',  'Jirengu')
  response.end('hello world')
})

console.log('open http://localhost:8080')
server.listen(8080)

step1:搭建靜態服務器

var http = require('http')
var path = require('path')
var fs = require('fs')
var url = require('url')

function staticRoot(staticPath, req, res){

  var pathObj = url.parse(req.url, true)
  var filePath = path.resolve(staticPath, pathObj.pathname.substr(1))
  var fileContent = fs.readFileSync(filePath,'binary')

  res.writeHead(200, 'Ok')
  res.write(fileContent, 'binary')
  res.end()
}

var server = http.createServer(function(req, res){
  staticRoot(path.resolve(__dirname, 'static'), req, res)
})

server.listen(8080)
console.log('visit http://localhost:8080' )


step2: 解析路由


var http = require('http')
var path = require('path')
var fs = require('fs')
var url = require('url')

var routes = {
  '/a': function(req, res){
    res.end('match /a, query is:' + JSON.stringify(req.query))
  },

  '/b': function(req, res){
    res.end('match /b')
  },

  '/a/c.js': function(req, res){
    res.end('match /a/c.js')
  },

  '/search': function(req, res){
    res.end('username='+req.body.username+',password='+req.body.password)
  }

}


var server = http.createServer(function(req, res){
  routePath(req, res)
})

server.listen(8080)
console.log('visit http://localhost:8080' )


function routePath(req, res){
  var pathObj = url.parse(req.url, true)
  var handleFn = routes[pathObj.pathname]
    
  if(handleFn){
    req.query = pathObj.query

    var body = ''
    req.on('data', function(chunk){
      body += chunk
    }).on('end', function(){
      req.body = parseBody(body)
      handleFn(req, res)
    })
    
  }else {
    staticRoot(path.resolve(__dirname, 'static'), req, res)
  }
}

function staticRoot(staticPath, req, res){
  var pathObj = url.parse(req.url, true)
  var filePath = path.resolve(staticPath, pathObj.pathname.substr(1))

  fs.readFile(filePath,'binary', function(err, content){
    if(err){
      res.writeHead('404', 'haha Not Found')
      return res.end()
    }

    res.writeHead(200, 'Ok')
    res.write(content, 'binary')
    res.end()  
  })

}

function parseBody(body){
  var obj = {}
  body.split('&').forEach(function(str){
    obj[str.split('=')[0]] = str.split('=')[1]
  })
  return obj
}


step3:Express 雛形

文件目錄結構

bin
  - www
lib
  - express.js
app.js

可通過 node bin/www 命令啟動服務器

bin/www

var app = require('../app')
var http = require('http')

http.createServer(app).listen(8080)
console.log('open http://localhost:8080')

app.js


var express = require('./lib/express')
var path = require('path')



var app = express()

app.use(function(req, res, next) {
  console.log('middleware 1')
  next()
})

app.use(function(req, res, next) {
  console.log('middleware 12')
  next()
})


app.use('/hello', function(req, res){
  console.log('/hello..')
  res.send('hello world')
})

app.use('/getWeather', function(req, res){
  res.send({url:'/getWeather', city: req.query.city})
})

app.use(function(req, res){
  res.send(404, 'haha Not Found')
})

module.exports = app

lib/express.js

var url = require('url')


function express() {

  var tasks = []

  var app = function(req, res){
    makeQuery(req)
    makeResponse(res)
        //post 的解析未實現

    var i = 0

    function next() {
      var task = tasks[i++]
      if(!task) {
        return
      }

      //如果是普通的中間件 或者 是路由中間件  
      if(task.routePath === null || url.parse(req.url, true).pathname === task.routePath){
        task.middleWare(req, res, next)
      }else{
        //如果說路由未匹配上的中間件,直接下一個
        next()
      }
    }

    next()
  }

  app.use = function(routePath, middleWare){
    if(typeof routePath === 'function') {
      middleWare = routePath
      routePath = null
    }
        
    tasks.push({
      routePath: routePath,
      middleWare: middleWare
    })
  }


  return app

}

express.static = function(path){

  return function(req, res){
            //未實現
  }
}

module.exports = express

function makeQuery(req){
  var pathObj = url.parse(req.url, true)
  req.query = pathObj.query
}

function makeResponse(res){
  res.send = function(toSend){
    if(typeof toSend === 'string'){
      res.end(toSend)
    }
    if(typeof toSend === 'object'){
      res.end(JSON.stringify(toSend))
    }
    if(typeof toSend === 'number'){
      res.writeHead(toSend, arguments[1])
      res.end()
    }
  }
}


step4: 框架完善

文件目錄結構

bin
  - www
lib
  - express.js
  - body-parser.js
app.js

可通過 node bin/www 命令啟動服務器

bin/www

var app = require('../app')
var http = require('http')

http.createServer(app).listen(8080)
console.log('open http://localhost:8080')

app.js


var express = require('./lib/express')
var path = require('path')
var bodyParser = require('./lib/body-parser')


var app = express()


//新增 bodyParser 中間件
app.use(bodyParser)

//新增 express.static 方法設置靜態目錄
app.use(express.static(path.join(__dirname, 'static')))


app.use(function(req, res, next) {
  console.log('middleware 1')
  next()
})

app.use(function(req, res, next) {
  console.log('middleware 12')
  next()
})


app.use('/hello', function(req, res){
  console.log('/hello..')
  res.send('hello world')
})

app.use('/getWeather', function(req, res){
  res.send({url:'/getWeather', city: req.query.city})
})

app.use('/search', function(req, res){
  res.send(req.body)
})

app.use(function(req, res){
  res.send(404, 'haha Not Found')
})


module.exports = app


lib/express.js

var url = require('url')
var fs = require('fs')
var path = require('path')


function express() {

  var tasks = []

  var app = function(req, res){

    makeQuery(req)
    makeResponse(res)
    console.log(tasks)

    var i = 0

    function next() {
      var task = tasks[i++]
      if(!task) {
        return
      }
      if(task.routePath === null || url.parse(req.url, true).pathname === task.routePath){
        task.middleWare(req, res, next)
      }else{
        next()
      }
    }

    next()
  }

  app.use = function(routePath, middleWare){
    if(typeof routePath === 'function') {
      middleWare = routePath
      routePath = null
    }

    tasks.push({
      routePath: routePath,
      middleWare: middleWare
    })
  }


  return app

}

express.static = function(staticPath){

  return function(req, res, next){
    var pathObj = url.parse(req.url, true)
    var filePath = path.resolve(staticPath, pathObj.pathname.substr(1))
    console.log(filePath)
    fs.readFile(filePath,'binary', function(err, content){
      if(err){
        next()
      }else {
        res.writeHead(200, 'Ok')
        res.write(content, 'binary')
        res.end()         
      }
    })
  }
}

module.exports = express


function makeQuery(req){
  var pathObj = url.parse(req.url, true)
  req.query = pathObj.query
}

function makeResponse(res){
  res.send = function(toSend){
    if(typeof toSend === 'string'){
      res.end(toSend)
    }
    if(typeof toSend === 'object'){
      res.end(JSON.stringify(toSend))
    }
    if(typeof toSend === 'number'){
      res.writeHead(toSend, arguments[1])
      res.end()
    }
  }
}

lib/body-parser.js


function bodyParser(req, res, next){
    var body = ''
    req.on('data', function(chunk){
      body += chunk
    }).on('end', function(){
      req.body = parseBody(body)
      next()
    })
}

function parseBody(body){
  var obj = {}
  body.split('&').forEach(function(str){
    obj[str.split('=')[0]] = str.split('=')[1]
  })
  return obj
}

module.exports = bodyParser
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 剛接觸 js 的同學在學到 ajax 的時候經常會懵掉,歸根結底就是對所謂的“后臺”、“服務器”這些概念沒有任何概...
    盒子愛上貓閱讀 1,702評論 0 6
  • 前言 剛接觸 js 的同學在學到 ajax 的時候經常會懵掉,歸根結底就是對所謂的“后臺”、“服務器”這些概念沒有...
    饑人谷_茜茜閱讀 262評論 0 0
  • 無我 自我 忘我 時間 天命 人說 我能想到的主題被時光篩去 留下的意識淡淡 菊叢里剛閃過一道人影 或許是只貓罷
    翔于閱讀 169評論 0 0
  • 看到你, 便忘記天空孤獨的樣子, 似乎本該如此。 語言, 才是最無力的表達, 難辨真假。 駐足靜默, 相視而笑, ...
    芯滿亦足閱讀 377評論 0 5
  • 中國的市場蛋糕:(現狀、需求、供應關系、等) 中國經濟轉型、中國新型政商關系 全球經濟增速、全球化進程 科技創新、...
    西陽關閱讀 136評論 0 1