node之http網(wǎng)絡(luò)服務(wù)

一. 簡單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 body parameter can be a Buffer object, a String, an object, or an Array.

發(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-Typeapplication/octet-stream
除非我們自定義了:

res.set('Content-Type', 'text/html')
res.send(Buffer.from('<p>some html</p>'))

(2) 如果是一個(gè)string,則默認(rèn)設(shè)置了Content-Typetext/html

res.send('<p>some html</p>')

(3)如果是一個(gè)數(shù)組或者對(duì)象,則默認(rèn)設(shè)置了Content-Typeapplication/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")

參考express文檔

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

npm koa地址

官方解釋,這是個(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');
});
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容