初識thrift
thrift 是 facebook 於2007年開發的一款跨平台 RPC(Remote Procedure Call) 軟件框架,
它可以在多種平台上進行無縫交互,數據傳輸使用二進制的方式,比XML和JSON體積更小,適合於內網的之間的數據進行交互。
thrift 結構
(參見https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/)
thrift 是由傳輸層、協議層和業務組成。用戶選定的傳輸層類型、協議層類型之后,只需要關注業務代碼即可,無需關注底層實現。
當生成了一套協議后,由客戶端和和服務端根據協議文件生成 thrift 的接口庫,
接口庫會提供定義的service方法,直接調用方法,遠程服務端會返回數據。
thrift類型
接口定義地址;
http://thrift.apache.org/docs/types
基本類型:
- bool 對應js中boolean
- byte 8位無符號整數
- i16 16位無符號整數
- i32 32位無符號整數
- i64 64位無符號整數
- double 64位符點型數字
- string utf-8類型的字符串
- binary 一系列基於string類型的編碼的字符串
與Js對應的對象類型,
- struct 結構體,可使用基本類型,強類型,需要定義不同的名稱
示例:
struct ListItem {
1: i32 id,
2: string content = '',
3: Status status = 1,
4: string author,
5: i32 textLength
}
容器類型,是不能直接在外層定義的,需要在 struct 中定義或者在 service 中定義,
主要包括:
與Js的數組對應的類型:
- list 一系列的數組元素
與Js中set對應的類型:
- set 相當於不重復的數組
與Js中Map對應的類型
- map 類似於字典模式
其他類型;
異常類型
- exception
可調用接口
- service
示例:
service Todo {
list<ListItem> getTodoList(),
i32 getTotalLength(1: string author),
i8 postTodo(1: PostItem item)
ListItem doneArtical(1: i32 id)
ListItem deleteArtical(1: i32 id)
}
使用thrift實現網頁和nodejs服務端進行交互的實踐
安裝環境
為方便操作,使用vue進行html的開發。首先,需要安裝thrift環境(在mac環境下,其他環境請參考http://thrift.apache.org/tutorial/):
brew install thrift
同時安裝vue開發的環境,vue/cli,用於直接對單個文件進行開發(其實是為了省事,不想搭webpack環境)。
npm install -g @vue/cli
npm install -g @vue/cli-service-global
新建接口文件
接口文件是我們根據 thrift 定義的類型進行書寫。其中除service類型外,其他定義都相當於定義成全局的類型,要注意名字的唯一性,service 是供我們調用的類型,就是接口。
創建的thrift文件如下:
enum Status {
NORMAL = 1,
DONE = 2,
DELETED = 3
}
struct PostItem {
1: string content = '',
2: string author,
}
exception CodeError {
1: i32 code = 0,
2: string message = ''
}
struct ListItem {
1: i32 id,
2: string content = '',
3: Status status = 1,
4: string author,
5: i32 textLength
}
service Todo {
list<ListItem> getTodoList(),
i32 getTotalLength(1: string author),
i8 postTodo(1: PostItem item)
ListItem doneArtical(1: i32 id)
ListItem deleteArtical(1: i32 id)
}
Todo就是我們需要使用的類。
生成接口庫文件
thrift -r --gen js:node todo.thrift && thrift -r --gen js todo.thrift
js:node 是供 Nodejs 調用的庫文件,js 是瀏覽器環境的文件(貌似是需要使用grunt進行打包,反正我是用不習慣,只是個示例,直接中在html通過腳本引入了)。生成的文件保存在gen-js
和 gen/nodejs
兩個文件夾下,一個是類型文件,一個是接口文件。
建立 server 端代碼
由於瀏覽器和后台交互目前只支持 ajax 的方式,所以我們的服務端是需要搭建http服務器的。
使用 thrift 的 createWebServer即可(注意不要使用示例中的createServer,那個創建的是socket服務,不是Http服務)。同時設置好傳輸協議為json格式,傳輸層類型為buffer模式。為接口中的每個 service 添加實現方式。
const thrift = require('thrift')
const Todo = require('./gen-nodejs/Todo')
const tTypes = require('./gen-nodejs/todo_types')
const data = []
let gid = 0
const actions = {
getTodoList () {
return data
},
getTotalLength () {
return data.length
},
postTodo (item) {
const result = new tTypes.ListItem({
content: item.content,
author: item.author,
status: tTypes.Status.NORMAL,
textLength: item.content.length,
id: ++gid
})
data.push(result)
return 0
},
doneArtical (id) {
const result = data.find(item => item.id === id)
if (!result) {
throw new tTypes.CodeError({code: 1, message: '請選擇條目!'})
}
result.status = tTypes.Status.DONE
return result
},
deleteArtical (id) {
const index = data.findIndex(item => item.id === id)
const result = data[index]
if (!~result) {
throw new tTypes.CodeError({code: 1, message: '請選擇條目!'})
}
data.splice(index, 1)
return result
}
}
const serverOptions = {
// 靜態文件服務器路徑
files: '.',
// 設置跨域請求
cors: {
'*': true
},
services: {
// 設置service
'/': {
// 傳輸層類型為buffer模式
transport: thrift.TBufferedTransport,
// 協議類型為json格式
protocol: thrift.TJSONProtocol,
processor: Todo,
handler: actions,
}
}
}
const server = thrift.createWebServer(serverOptions)
server.listen(7878, () => {
console.log(`監聽端口:${7878}`)
})
創建瀏覽器端代碼
瀏覽器代碼就是寫網頁了。為了使用 vue 的 serve 功能,網頁的名稱需要設置為 App.vue,同時添加個自定義的 html 文件,添加引入 thrift 庫腳本:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>新增文章</title>
<meta name="viewport" id="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<link rel="shortcut icon" href="/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
</head>
<body>
<div id="app"></div>
<script type='text/javascript' src='http://localhost:7878/thrift-bundle.js'></script>
</body>
</html>
vue文件內容為:
<template>
<div>
<section class="artical-list">
<ul>
<li
v-for="(item, index) in list"
:key="index">
<p>{{item.content}}</p>
<p>作者: {{item.author}}, 當前狀態:{{item.status | status}}</p>
<button @click="doneArtical(item)">設置為已閱</button>
<button @click="deleteArtical(item)">刪除</button>
</li>
</ul>
</section>
<section class="form-data">
<textarea name="artical" v-model="artical" cols="30" rows="10"></textarea>
<input type="text" name="author" v-model="author"/>
<button @click="postArtical">提交</button>
</section>
</div>
</template>
<script>
/* eslint-disable */
export default {
data () {
return {
list: [],
artical: '',
author: '',
}
},
created () {
this.init()
},
filters: {
status (value) {
const status = ['無', '正常', '已閱', '刪除']
return status[value]
},
},
methods: {
init () {
const transport = new Thrift.Transport('http://localhost:7878')
const protocol = new Thrift.Protocol(transport)
const client = new TodoClient(protocol)
this.client = client
this.getList()
},
getList () {
this.client.getTodoList((result) => {
this.list = result
})
},
postArtical () {
const result = new PostItem()
result.content = this.artical
result.author = this.author
this.client.postTodo(result, (result) => {
this.getList()
})
},
doneArtical (item) {
this.client.doneArtical(item.id, (result) => {
if (result instanceof Thrift.TApplicationException) {
alert(result.message)
return
}
this.getList()
})
},
deleteArtical (item) {
this.client.deleteArtical(item.id, (result) => {
if (result instanceof Thrift.TApplicationException) {
alert(result.message)
return
}
this.getList()
})
},
},
}
</script>
主要思路是在初始化先創建接口的實例,設置 transport 的請求地址,然后使用我們定義的 service,
綁定實例在this上。每次要操作時直接調用實例的方法即可。看起來是不是和我們寫普通封裝好的axios一樣?
運行
為方便使用,我們使用 nodemon 進行服務器開發自動重啟,我們在 npm 包中添加以下腳本:
"scripts": {
"start": "vue serve & node server.js",
"dev": "vue serve & npm run compile && nodemon server.js",
"compile": "npm run gen && npm run concat",
"gen": "thrift -r --gen js:node todo.thrift && thrift -r --gen js todo.thrift",
"concat": "concat -o thrift-bundle.js ./thrift.js ./gen-js/*.js"
},
這樣,我們使用 npm start
啟動已經構建好的服務,使用 npm run dev
進行開發,
使用 npm run compile
在改動了 thrift 接口文件后進行重新編譯。
這樣我們的網頁就做好了:
總結
搭建一個簡單的 thrift 項目還是很容易的,所有的代碼已經放在我的github上https://github.com/wenlonghuo/code-test/tree/master/004_thrift。
其他原理和總結有待后續挖掘。