剛接觸 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,加群暗號:框架-簡書,最新的群公告有參加直播的方式~
關于筆者
若愚,曾經在百度、阿里巴巴做前端開發,后創業加入饑人谷做前端老師。親手培養了近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