一. 簡單HTTP服務(wù)器
http服務(wù)作為前端人員來說,比較熟悉,具體就不介紹了。
(1)通過瀏覽器來訪問,請(qǐng)求資源的方式
下載:
npm install http
// or
yarn add http
代碼:
const http = require("http")
http.createServer(function(request, response) {
console.log(request.url)
if(request.url == '/favicon.ico') {
response.writeHead(200)
request.end();
return
}
response.writeHead(200);
res.setHeader('Content-Type', 'text/plain;charset=utf-8');
response.end("你好")
})
.listen(3000)
在瀏覽器輸入localhost:3000,從這段代碼運(yùn)行結(jié)果種,我們可以發(fā)現(xiàn)打印出了/和/favicon.ico,實(shí)際我們是訪問了/,返回了你好的文本,如果返回的是中文需要設(shè)置setHeader;后面那個(gè)是瀏覽器的默認(rèn)行為,會(huì)去尋找網(wǎng)頁顯示的圖標(biāo)
如果我們想要根據(jù)url返回文件,則
const http = require("http")
const fs = require('fs')
http.createServer(function(request, response) {
console.log(request.url);
if(request.url == '/favicon.ico') {
response.writeHead(200)
response.end();
return
}
response.writeHead(200);
fs.createReadStream(__dirname + '/pages/hello.html').pipe(response)
})
.listen(3000)
使用fs讀取html文件流,__dirname 表示當(dāng)前目錄,并將結(jié)果通過pip管道傳輸給response
(2)請(qǐng)求url中帶有參數(shù)
這時(shí)候的request.url是包含請(qǐng)求路徑,請(qǐng)求參數(shù),所以我們需要使用url來獲取
const url = require('url')
console.log(url.parse(request.url))
//訪問: localhost:3000/test?id=1結(jié)果
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?id=1',
query: 'id=1',
pathname: '/',
path: '/?id=1',
href: '/?id=1'
}
從打印結(jié)果可以得到:url.pathname才是路徑,url.query可以獲取參數(shù),但是又需要解析,這時(shí)候引入querystring
const querystring = require("querystring")
const query = querystring.decode(url.query)
const id = query.id
二. Express
1. 介紹
(1)express是什么呢?
npm expree 官網(wǎng)的解釋如下:
- Robust routing
- Focus on high performance
- Super-high test coverage
- HTTP helpers (redirection, caching, etc)
- View system supporting 14+ template engines
- Content negotiation
- Executable for generating applications quickly
- 健壯的路由功能(一個(gè)請(qǐng)求包通過服務(wù)器分發(fā)的過程稱為路由)
- 專注高性能
- 高測(cè)試覆蓋率
- http重定向功能(302)
- 模板引擎
- accept內(nèi)容協(xié)商
- 提供強(qiáng)大腳手架
其實(shí)就是一個(gè)獨(dú)立的路由和中間件web框架
(2)下載
cnpm install express --save
(3)代碼
const express = require("express");
const app = express();
const url = require('url')
const querystring = require("querystring")
app.get("/favicon.ico", function(req, res) {
res.writeHead(200);
res.end();
return;
})
app.get("/test", function(req, res) {
const query = req.query
res.setHeader('Content-Type', 'text/plain;charset=utf-8');
res.writeHead(200);
// 可以用res.status(200)代替
res.end("id" + query.id);
// 可以用res.send("....")代替
return;
})
app.listen(3000)
我們可以直接使用url匹配,獲取參數(shù)也不需要querystring了。如果是返回一個(gè)html文件,則如下所示:
app.get("/test", function(req, res) {
res.status(200);
res.send(fs.readFileSync(__dirname + '/pages/hello.html', "utf-8"));
})
那么,問題來了:
2. res.end, res.send, res.json的區(qū)別在哪呢?
- res.end()
Ends the response process. Use to quickly end the response without any data.
用于快速響應(yīng),不需要數(shù)據(jù)的情況,比如res.status(404).end()
- res.send([body])
Sends the HTTP response. The
bodyparameter can be aBufferobject, aString, an object, or anArray.
發(fā)送http響應(yīng),body部分可以是buffer對(duì)象,string字符串,一個(gè)對(duì)象,或者是一個(gè)數(shù)組
res.send({ some: 'json' })
res.send('<p>some html</p>')
res.status(404).send('Sorry, we cannot find that!')
res.status(500).send({ error: 'something blew up' })
這個(gè)方法對(duì)簡單的的非流式響應(yīng),執(zhí)行了有用的任務(wù),例如,它自動(dòng)分配Content-Length HTTP響應(yīng)標(biāo)頭字段(除了先前定義),并提供自動(dòng)的HEAD和HTTP實(shí)時(shí)緩存支持。
(1) 如果是一個(gè)buffer對(duì)象,則默認(rèn)設(shè)置了Content-Type為application/octet-stream
除非我們自定義了:
res.set('Content-Type', 'text/html')
res.send(Buffer.from('<p>some html</p>'))
(2) 如果是一個(gè)string,則默認(rèn)設(shè)置了Content-Type為text/html
res.send('<p>some html</p>')
(3)如果是一個(gè)數(shù)組或者對(duì)象,則默認(rèn)設(shè)置了Content-Type為application/json
res.send({ user: 'tobi' })
res.send([1, 2, 3])
- res.json()
發(fā)送JSON響應(yīng), 此方法發(fā)送響應(yīng)(具有正確的內(nèi)容類型),發(fā)送的是json字符串。這個(gè)和上面的res.send的第三種方式一樣,都是將Content-Type設(shè)為application/json,但是區(qū)別是,可以將其他類型,比如: object, array, string, Boolean, number, 或者 null
res.json(null)
res.json({ user: 'tobi' })
res.status(500).json({ error: 'message' })
①res.json("1")

②res.send("1")

3. express中間件和next()
中間件是指可以訪問請(qǐng)求對(duì)象request和響應(yīng)對(duì)象response以及next()應(yīng)用程序請(qǐng)求。
next表示可以繼續(xù)執(zhí)行下一個(gè)中間件。
這些中間件的執(zhí)行就像洋蔥一樣:
const express = require('express')
const app = express()
app.use(function middleware1(req, res, next) {
console.log('1 開始')
next()
console.log('1 結(jié)束')
})
app.use(function middleware2(req, res, next) {
console.log('2 開始')
next()
console.log('2 結(jié)束')
})
app.get('/', function handler(req, res) {
res.send('ok')
console.log('=================')
})
app.listen(8080)
執(zhí)行結(jié)果:
1 開始
2 開始
=================
2 結(jié)束
1 結(jié)束
express的缺點(diǎn)就是不能很好處理異步請(qǐng)求的函數(shù),我們可以來看一下,如果在中間件中執(zhí)行了異步函數(shù)
let count = 0;
app.use(function middleware1(req, res, next) {
console.log('1 開始')
next()
if(res.win) {
count++;
}
console.log('1 結(jié)束')
console.log(count)
})
app.use(function middleware2(req, res, next) {
console.log('2 開始')
setTimeout(() => {
console.log("異步函數(shù)執(zhí)行完了");
res.win = true
}, 500)
next()
console.log('2 結(jié)束')
})
app.get('/', function handler(req, res) {
res.send('ok')
console.log('=================')
})
app.listen(8080)
執(zhí)行結(jié)果:
1 開始
2 開始
=================
2 結(jié)束
1 結(jié)束
0
異步函數(shù)執(zhí)行完了
我們可以看出這個(gè)next并不會(huì)等待異步函數(shù)執(zhí)行完再去執(zhí)行下一步,而是直接結(jié)束,而異步函數(shù)通過一個(gè)新的調(diào)用棧去這行這個(gè)代碼,導(dǎo)致最終獲取的count并沒有被加上1。如果再加上延時(shí)獲取數(shù)據(jù)的代碼,則發(fā)現(xiàn)可以加1。
app.use(function middleware1(req, res, next) {
console.log('1 開始')
next()
if(res.win) {
count++;
}
console.log('1 結(jié)束')
console.log(count)
setTimeout(() => {
if(res.win) {
count++;
}
console.log(count);
}, 1000)
})
執(zhí)行結(jié)果:
1 開始
2 開始
=================
2 結(jié)束
1 結(jié)束
0
異步函數(shù)執(zhí)行完了
1
為了解決這個(gè)缺點(diǎn),就誕生了koa。
三、Koa
Koa is a middleware framework that can take two different kinds of functions as middleware:
- async function
- common function
官方解釋,這是個(gè)koa中間件框架,主要有兩大功能:
- 使用async function 實(shí)現(xiàn)的中間件
- 有“暫停執(zhí)行”的能力
- 在異步的情況下也能符合洋蔥模型
- 有conntext, request, response處理(采用context.response, context.request訪問)
- 精簡內(nèi)核,所有額外功能都移動(dòng)到中間件里實(shí)現(xiàn),提高程序的靈活性和性能
跟express不同的是,去掉了路由,如果想要,我們可以安裝koa-mount,作用是:掛載其他Koa應(yīng)用程序作為中間件。
安裝
npm i koa koa-mount
代碼
const koa = require('koa')
const mount = require('koa-mount')
const fs = require("fs")
let count = 0;
const app = new koa()
app.use(
mount('/favicon.ico', function(ctx) {
ctx.sttaus = 200;
})
)
app.use(
mount('/test', function(ctx) {
ctx.status = 200
ctx.body = fs.readFileSync(__dirname + '/pages/hello.html', 'utf-8')
}, )
)
app.listen(3000)
我們可以在mount中設(shè)置對(duì)應(yīng)的koa實(shí)例,在koa實(shí)例中使用更多的中間件
const koa = require('koa')
const mount = require('koa-mount')
const fs = require("fs")
let count = 0;
const app = new koa()
const gameKoa = new koa()
gameKoa.use(
async function(ctx, next) {
console.log('1 開始')
await next()
if(ctx.response.win) {
count++;
}
console.log('1 結(jié)束')
console.log(count)
}
)
gameKoa.use(
async function(ctx, next) {
console.log('2 開始')
await new Promise((resolve, reject) => {
setTimeout(() => {
ctx.status = 200
ctx.body = 'yes'
console.log("異步函數(shù)執(zhí)行完了");
ctx.response.win = true
resolve()
}, 500)
})
console.log('2 結(jié)束')
next()
}
)
app.use(
mount('/game', gameKoa)
)
app.listen(3000)
執(zhí)行結(jié)果:
1 開始
2 開始
異步函數(shù)執(zhí)行完了
2 結(jié)束
1 結(jié)束
1
這時(shí)候count的值達(dá)到了我們預(yù)期想要的結(jié)果
Express和Koa的比較
① express 門檻更低,koa更強(qiáng)大優(yōu)雅
② express封裝更多東西,開發(fā)更快速,可定制型更高
四 、express-generator
express-generator相當(dāng)于express的腳手架工具,對(duì)應(yīng)kao也有koa-generator,那么接下來,
就來講一下如何使用express-generator運(yùn)行打包后的react項(xiàng)目
4.1 安裝
npm install express
npm install -g express-generator
4.2新建
①express demo
不設(shè)定模板,view目錄下文件默認(rèn)使用jade模板
②express --view=pug ./temp/demo
切換到./temp/demo,view目錄下的文件默認(rèn)使用pug模板
③express demo -e
view目錄下文件默認(rèn)使用ejs模板
4.3目錄結(jié)構(gòu)
—demo
——bin // 其中有www啟動(dòng)文件,可以設(shè)置啟動(dòng)端口號(hào)
——public // 存放靜態(tài)文件
——routes // 路由定義url和資源的映射
——views // 放置模板文件
——app.js // 入口文件
——package.json //依賴
4.4react的打包
(1)homepage配置
在package.json文件中配置
"homepage": "/"
設(shè)置為在服務(wù)器的那個(gè)目錄下
(2)url配置
在url的請(qǐng)求中配置,生產(chǎn)環(huán)境為具體的地址
(3)配置打包路徑
使用react-app-rewired,可以在不eject暴露所有配置文件的情況下,更改配置文件
在根目錄下的config-overrides.js文件中
const {
override,
fixBabelImports,
addLessLoader,
} = require("customize-cra");
module.exports = override(
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: "css"
}),
addLessLoader({
strictMath: true,
noIeCompat: true,
localIdentName: "[local]--[hash:base64:5]" // if you use CSS Modules, and custom `localIdentName`, default is '[local]--[hash:base64:5]'.
}),
// 在此處配置
(config) => {
const path = require('path');
const paths = require('react-scripts/config/paths');
// 修改配置里的appBuild目錄
paths.appBuild = path.join(path.dirname(paths.appBuild), 'manager');
// 修改項(xiàng)目打包地址
config.output.path = path.join(path.dirname(config.output.path), 'manager');
return config;
}
);
使用yarn build打包,即可發(fā)現(xiàn)有了manager文件
在serve.js中配置
const express = require('express')
var path = require('path');
var proxy = require('http-proxy-middleware')
const app = express()
var targetPath1 = "https://i.cn"
var targetPath2 = "https://i:2443"
var proxyOption1 = {
target: targetPath1,
changeOrigoin: true,
ws: true,
}
var proxyOption2 = {
target: targetPath2,
changeOrigoin: true,
ws: true,
pathRewrite: { '/api': '/' }
}
app.use(express.static(__dirname, "/manager"));
app.use('/micro-course', proxy.createProxyMiddleware(proxyOption1))
app.use('/api', proxy.createProxyMiddleware(proxyOption2))
app.get('*', (req, res) => res.sendFile('index.html', { root: __dirname + '/manager' }))
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
app.set('port', 3000)
app.listen(3000, function() {
console.log('Listening on port 3000');
});