1. KOA2—— Node .jsプラットフォームに基づくWebサーバー フレームワーク
Expressのオリジナル チームによって構築されました
Express Koa と Koa 2は両方ともWebサーバー用のフレームワークであり 、それらの間の違いと関係は次の表で表すことができます。
フレームワーク名 |
効果 |
非同期処理 |
特急_ |
ウェブフレームワーク |
コールバック関数 |
それで |
ウェブフレームワーク |
発電機+発電量 |
コア2_ |
ウェブフレームワーク |
非同期/待機 |
環境は Node v7.6.0以降に依存します
Koa2 はasyncとawaitをサポートしている ため、Nodeのバージョンに関する要件があります。糖衣構文のasyncとawaitはNode 7.6以降でのみサポートされるため、Nodeのバージョンが少なくともレベル7.6である必要があります。
1.1オニオンモデルのミドルウェア
下図に示すように、サーバーは実際にはリクエストを次々と処理することになっており、 Webサーバーはブラウザから 送信されたリクエストを次々と受信すると、次々とレスポンスを作成してブラウザに返します。サーバーに到達するには プログラム が処理する必要があります。プログラムが処理された後、応答が形成されてブラウザに返されます。サーバーがリクエストを処理するプログラムは、 Koa 2の世界ではミドルウェアと呼ばれます。
この種のミドルウェアは1 つだけではなく、複数存在する場合があります。たとえば、上図のように3層のミドルウェアがあり、リクエストの処理プロセスと呼び出しの順序は次のとおりです。
リクエストがサーバーに到着すると、ミドルウェアの最初の層が最初にリクエストを処理します。
第 1 層ミドルウェアがリクエストを処理した後、リクエストを第 2 層ミドルウェアに渡します。
第 2 層ミドルウェアがリクエストを処理した後、リクエストを第 3 層ミドルウェアに送信します。
第 3層ミドルウェア内にはミドルウェアがないため、第 3 層ミドルウェアがすべてのコードを処理した後、リクエストは返されます。
これは第 2 レベルのミドルウェアに到達するため、第 2 レベルのミドルウェアはこのリクエストを 2 回処理します。
リクエストを処理した後、第 2層のミドルウェアが第 1 層のミドルウェアに到達するため、第 1 層のミドルウェアもこのリクエストに応答します。
リクエストが 2 回処理され
この呼び出しシーケンスはオニオン モデルです。ミドルウェアはリクエストの処理において先入れ後出しの感覚を持ちます。リクエストは最初に第 1 レベルの ミドルウェア、最後に第 1 レベルのミドルウェアがリクエストを再度処理します。
2. KOA2のクイックスタート
ノード のバージョンを確認する
node -vコマンドは、 Nodeのバージョンを確認するのに役立ちます。Koa 2を使用するには、 Nodeバージョン7.6以降が必要です。
Koa 2をインストールする
このコマンドは、プロジェクト内のサードパーティ パッケージの情報を維持できるpackage.jsonファイルを迅速に作成できます。
npm インストール koa
このコマンドはkoa onlineの最新バージョンを 現在のプロジェクトにダウンロードできます。koa onlineの最新バージョンはkoa 2であるため、npm install koa 2を実行する必要はありません。
エントリー ファイルapp.jsを記述し、Koaのインスタンスオブジェクトを作成します。
// 1. koaオブジェクトを作成する_ const Koa = require ( ' koa ' ) //コンストラクターをインポートします const app = new Koa () //構築メソッドを通じてインスタンス オブジェクトを作成します |
応答関数を作成する(ミドルウェア)
応答関数は使用することによってのみ有効になります。この関数には 2 つのパラメータがあり、1 つはctxで、もう 1 つはnext ctxです。
コンテキストは、リクエストが配置されているWebコンテナを指します。ctx.requestを通じてリクエスト オブジェクトを取得するか、ctx.responseを通じて応答オブジェクトを取得できます。
next :内部
// 2.応答関数を書く(ミドルウェア) アプリ。use (( ctx , next ) => { コンソール。ログ( ctx .リクエスト.url ) _ ctx 。応答。body = ' hello world ' } ) |
ポート番号 を指定するapp.listenを通じてポート番号を指定できます。
// 3.ポート番号300 0をバインドします アプリ。聞いてください( 3000 ) |
サーバーの起動:ノードapp .jsを通じて サーバーを起動できます。
次に、ブラウザを開き、ブラウザに「127.0.0.1:3000/」と入力します。 ブラウザにhello worldの文字列が表示され、サーバーのターミナルに要求されたURLも表示されます。
3. KOA2ミドルウェアの特徴
Koa 2のインスタンス オブジェクトは、useメソッドを通じて ミドルウェアを追加します。ミドルウェアは関数であり、この関数にはctxとnextという 2 つのパラメータがあります。ミドルウェアの実行はオニオンモデルに準拠しており、内側のミドルウェアが実行できるかどうかは、外側のミドルウェアの次の関数が呼び出されるかどうかによって決まります。次の関数を呼び出してPromiseオブジェクトを取得します。Promiseでラップされたデータを取得したい場合は、awaitとasyncを組み合わせます。
アプリ。use ( async ( ctx , next ) => { // ミドルウェアが実行したいことを入力するだけです 次を待ってください() //内層のミドルウェアがすべて終了した後にやりたいこと } ) |
4.バックグラウンドプロジェクトの開発
4.1.バックグラウンドプロジェクトの目標
1.サーバーがリクエストを処理するのに費やした合計時間を計算します
このリクエストに対してサーバーがすべてのミドルウェアで費やした合計時間を計算します。
2.応答コンテンツのMIMEタイプを応答ヘッダーに追加します
MIMEタイプを追加すると、ブラウザはサーバーから返されたデータをより適切に処理できるようになります。
フロントエンド ブラウザへの応答がjson形式のデータである場合、応答ヘッダーに Type を追加する必要があります。その値はapplication/jsonで、これはjsonデータ型のMIMEタイプです。
3. URLに従って、指定されたディレクトリ内のファイルの内容を読み取ります。
バックグラウンド サーバーのコードを簡素化するために、フロントエンド チャートに必要なデータはデータベースに存在しませんが、ファイルに保存されます。この操作はバックグラウンド コードを簡素化するためだけです。したがって、以下を読み取る必要があります。あるディレクトリ以下のファイル の。
各目標はミドルウェアが実装する必要がある機能であるため、バックグラウンド プロジェクトには 3 つのミドルウェアが必要です
4.2.バックグラウンドプロジェクトの開発手順
koa_serverという名前の新しいフォルダー。これはバックグラウンドプロジェクトのフォルダーです。
1.インストールパッケージ
npm init - y
npm インストール koa
2.ファイルとディレクトリ構造を作成します。
コードのディレクトリ構造
app.jsはバックグラウンドサーバーのエントリファイルです
データディレクトリは、すべてのモジュールの jsonファイル データを保存するために使用されます。
ミドルウェアはすべてのミドルウェア コードを保存するために使用されます
koa_response_data.jsはビジネスロジックミドルウェアです_ _ _
koa_response_duration.jsはサーバーの処理時間を計算するためのミドルウェアです。
koa_response_header.js は、レスポンスヘッダーを具体的に設定するために使用されるミドルウェアです
次に、 各モジュールのjsonデータ ファイルをデータディレクトリにコピーし、次のようにapp.jsファイルにコードを記述します。
//サーバーエントリーファイル // 1. KOAのインスタンスオブジェクトを作成する const Koa = require ( ' koa ' ) const app =新しい Koa ( ) // 2.ミドルウェアをバインドする //最初の層のミドルウェアをバインドします //第 2 層のミドルウェアをバインドする //第三層ミドルウェアをバインドする // 3.ポート番号888 8をバインドします アプリ。聞いてください( 8888 ) |
4.3.合計時間のかかるミドルウェア
1.レイヤー1ミドルウェア
合計時間消費ミドルウェアの機能は、サーバー上のすべてのミドルウェアの合計時間を計算することです。最初の層のミドルウェアは最初にリクエストを処理するミドルウェアであるため、最初の層に配置する必要があり ます。リクエストを最後に処理するミドルウェアでもあります。初めてミドルウェアに入るときは、開始時間を記録します。他のすべてのミドルウェアが実行された後、終了時間を記録し、その 2 つを減算して合計時間を取得します。
3.レスポンスヘッダーを設定する
将计算出来的结果,设置到响应头的 X-Response-Time 中, 单位是毫秒 ms
具体代码如下:
app.js
// 绑定第一层中间件 const respDurationMiddleware = require('./middleware/koa_response_duration') app.use(respDurationMiddleware) |
koa_response_duration.js
// 计算服务器消耗时长的中间件 module.exports = async (ctx, next) => { // 记录开始时间 const start = Date.now() // 让内层中间件得到执行 await next() // 记录结束的时间 const end = Date.now() // 设置响应头 X-Response-Time const duration = end - start // ctx.set 设置响应头 ctx.set('X-Response-Time', duration + 'ms') } |
4.4 响应头中间件
1.第2层中间件
这个第2层中间件没有特定的要求
2.获取 mime类型
由于响应给前端浏览器当中的数据都是 json 格式的字符串,所以 mime 类型可以统一
的给它写成 application/json , 当然这一块也是简化的处理了,因为 mime 类型有几十几百 种,,所以这里简化处理一下
3.设置响应头
响应头的key是 Content-Type ,它的值是 application/json , 顺便加上 charset=utf-8
告诉浏览器,我这部分响应的数据,它的类型是 application/json ,同时它的编码是具体代码如下:
app.js
// 绑定第二层中间件
const respHeaderMiddleware = require('./middleware/koa_response_header')
app.use(respHeaderMiddleware)
koa_response_header.js
// 设置响应头的中间件
module.exports = async (ctx, next) => {
const contentType = 'application/json; charset=utf-8'
ctx.set('Content-Type', contentType)
await next()
}
4.5 业务逻辑中间件
1.第3层中间件
第三层中间件处理实际的业务逻辑,处理前端请求并返回
2.读取文件内容
获取 URL 请求路径
const url = ctx.request.url |
根据URL请求路径,拼接出文件的绝对路径
let filePath = url.replace('/api', '') filePath = '../data' + filePath + '.json' filePath = path.join(__dirname, filePath) |
这个 filePath就是需要读取文件的绝对路径
读取这个文件的内容
使用 fs模块中的 readFile方法进行实现
3.设置响应体
ctx.response.body
具体代码如下:
app.js
// 绑定第三层中间件 const respDataMiddleware = require('./middleware/koa_response_data') app.use(respDataMiddleware) |
koa_response_data.js
// 处理业务逻辑的中间件 ,读取某个json文件的数据
const path = require('path')
const fileUtils = require('../utils/file_utils')
module.exports = async (ctx, next) => {
// 根据url
const url = ctx.request.url // /api/seller ../data/seller.json
let filePath = url.replace('/api', '') // /seller
filePath = '../data' + filePath + '.json' // ../data/seller.json
filePath = path.join(__dirname, filePath)
try {
const ret = await fileUtils.getFileJsonData(filePath)
ctx.response.body = ret
} catch (error) {
const errorMsg = {
message: '读取文件内容失败 , 文件资源不存在', status: 404
}
ctx.response.body = JSON.stringify(errorMsg) }
console.log(filePath)
await next()
}
file_utils.js
// 读取文件的工具方法
const fs = require('fs')
module.exports.getFileJsonData = (filePath) => {
// 根据文件的路径, 读取文件的内容
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (error, data) => {
if(error) {
// 读取文件失败
reject(error)
} else {
// 读取文件成功
resolve(data)
}
})
})
}
4.6 允许跨域
设置响应头koa_response_header.js 添加
// 设置响应头的中间件
module.exports = async (ctx, next) => {
const contentType = 'application/json; charset=utf-8'
ctx.set('Content-Type', contentType)
ctx.set("Access-Control-Allow-Origin", "*")
ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE")
await next()
}
5.引入webScoket
WebSocket可以保持着浏览器和客户端之间的长连接, 通过 WebSocket可以实现数据由后端推送到前 端,保证了数据传输的实时性. WebSocket涉及到前端代码和后端代码的改造
5.1. WebSocket 的使用
安装 WebSocket包
npm i ws -S
创建 service\web_socket_service.js 文件
创建 WebSocket实例对象
const WebSocket = require('ws')
// 创建WebSocket服务端的对象, 绑定的端口号是9998
const wss = new WebSocket.Server({
port: 9998
})
wss.on("connection", client => {
console.log("有客户端连接 ...")
client.on("message", msg => {
console.log("客户端发送数据过来了")
// 发送数据给客户端
client.send('hello socket')
})
})
监听事件
在 app.js 中引入 web_socket_service.js 这个文件, 并调用 listen 方法
const webSocketService = require('./service/web_socket_service')
webSocketService.listen()
5.2 约定客户端之间数据交互格式
客户端和服务端之间的数据交互采用 JSON 格式
客户端发送数据给服务端的字段如下:
{
"action": "getData",
"socketType": "trendData",
"chartName": "trend",
"value": ""
}
或者
{
"action": "fullScreen",
"socketType": "fullScreen",
"chartName": "trend",
"value": true
}
或者
{
"action": "themeChange",
"socketType": "themeChange",
"chartName": "",
"value": "chalk"
}
其中:
action : 代表某项行为,可选值有
- getData 代表获取图表数据
- fullScreen 代表产生了全屏事件
- themeChange 代表产生了主题切换的事件
socketType : 代表业务模块类型, 这个值代表前端注册数据回调函数的标识, 可选值有:
- trendData
- sellerData
- mapData
- rankData
- hotData
- stockData
- fullScreen
- themeChange
chartName : 代表图表名称, 如果是主题切换事件, 可不传此值, 可选值有:
- trend
- seller
- rank
- stock
value : 代表 具体的数据值, 在获取图表数据时, 可不传此值, 可选值有
如果是全屏事件, true代表全屏, false代表非全屏
如果是主题切换事件, 可选值有 chalk或者 vintage
服务端发送给客户端的数据如下:
{
"action": "getData",
"socketType": "trendData",
"chartName": "trend",
"value": "",
"data": "从文件读取出来的json文件的内容"
}
或者
{
"action": "fullScreen",
"socketType": "fullScreen",
"chartName": "trend",
"value": true
}
或者
{
"action": "themeChange", "socketType": "themeChange", "chartName": "",
"value": "chalk"
}
注意, 除了 action为 getData 时, 服务器会在客户端发过来数据的基础之上, 增加 data字段, 其他的情况, 服务器会原封不动的将从某一个客户端发过来的数据转发给每一个处于连接状态 的客户端
5.3.代码实现 service\web_socket_service.js
const path = require('path')
const fileUtils = require('../utils/file_utils')
const WebSocket = require('ws')
// 创建WebSocket服务端的对象, 绑定的端口号是9998
const wss = new WebSocket.Server({
port: 9998
})
// 服务端开启了监听
module.exports.listen = () => {
// 对客户端的连接事件进行监听
// client:代表的是客户端的连接socket对象
wss.on('connection', client => {
console.log('有客户端连接成功了...')
// 对客户端的连接对象进行message事件的监听
// msg: 由客户端发给服务端的数据
client.on('message',async msg => {
console.log('客户端发送数据给服务端了: ' + msg)
let payload = JSON.parse(msg)
const action = payload.action
if (action === 'getData') {
let filePath = '../data/' + payload.chartName + '.json'
// payload.chartName // trend seller map rank hot stock
filePath = path.join(__dirname, filePath)
const ret = await fileUtils.getFileJsonData(filePath)
// 需要在服务端获取到数据的基础之上, 增加一个data的字段
// data所对应的值,就是某个json文件的内容
payload.data = ret
client.send(JSON.stringify(payload))
console.log(payload.data)
} else {
// 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端
// wss.clients // 所有客户端的连接
wss.clients.forEach(client => {
console.log("客户端触发"+action+"事件")
// client.send(msg)
payload.data = JSON.parse(msg)
client.send(JSON.stringify(payload) )
})
}
// 由服务端往客户端发送数据
// client.send('hello socket from backend')
})
})
}