接下來我要作的是用fabric sdk來作出應用程序,代替CLI與整個區塊鏈網絡交互。而且實現一個http API,向社區提供一個簡單的接口,使社區輕鬆的與區塊鏈交互。javascript
官方雖然提供了Node.JS,Java,Go(最近剛出了python)等多種語言的SDK,可是不少SDK還不成熟和完善,有的甚至文檔都沒有。我使用Node.js的緣由有三。1.官方例子使用的是Node.js SDK 2.之前我作以太坊智能合約開發的時候也用過Node.js 3.最後是由於Node.js實現一個http服務器是很是簡單的。java
咱們能夠選擇將node.js安裝在本地(1.1介紹),或者將node應用部署在docker。須要注意的是fabric目前不支持node7.x版本,須要6.9.x或更高版本和NPM。node
本地安裝node的話咱們只須要執行如下命令便可安裝NodeJS的最新v6版本:python
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - sudo apt-get install -y nodejs
國內使用NPM會遇到不少問題,可用淘寶NPM鏡像:nginx
npm install -g cnpm --registry=https://registry.npm.taobao.org
使用方法是用cnpm代替npmweb
而我這裏主要介紹的方法是將node.js部署在docker。docker
sudo docker pull node:6.11.3
再提醒一遍fabric目前不支持node7.x版本,須要6.9.x或更高版本。這一步,也能夠省略,後面的Dockerfile文件,會自動拉取該鏡像。npm
mkdir -p node/mynodeapp && cd node/mynodeapp
touch package.json
vi package.json
{ "name": "nodeTest", "version": "1.0.0", "description": "Hyperledger Fabric Node SDK Test Application", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "fabric-ca-client": "^1.0.0", "fabric-client": "^1.0.0" }, "author": "Devin Zeng", "license": "Apache-2.0", "keywords": [ "Hyperledger", "Fabric", "Test", "Application" ] }
將與fabric交互的代碼放在這裏,監聽8888端口。json
touch server.js
vi server.js
因爲咱們此次是爲peer0.org2開發API,因此本機IP地址爲10.0.2.12,代碼以下promise
1 var http = require('http'); 2 var url = require('url'); 3 4 http.createServer(function(req, res){ 5 var arg = url.parse(req.url, true).query; //方法二arg => { aa: '001', bb: '002' } 6 console.log(arg.func);//返回001 7 if (arg.func == "queryPost" || arg.func == "richQueryPosts" || arg.func == "getPostNum"){ 8 query(arg); 9 }else if (arg.func == "addPost" || arg.func == "updatePost"){ 10 invoke(arg); 11 } 12 }).listen(8888);//創建服務器並監聽端口 13 14 console.log('Server running at http://127.0.0.1:8888/'); 15 16 17 18 19 20 function query(arg){ 21 'use strict'; 22 23 var hfc = require('fabric-client'); 24 var path = require('path'); 25 var sdkUtils = require('fabric-client/lib/utils') 26 var fs = require('fs'); 27 var options = { 28 user_id: 'Admin@org2.example.com', 29 msp_id:'Org2MSP', 30 channel_id: 'mychannel', 31 chaincode_id: 'mycc', 32 network_url: 'grpcs://10.0.2.12:7051',//由於啓用了TLS,因此是grpcs,若是沒有啓用TLS,那麼就是grpc 33 privateKeyFolder: path.join(__dirname,'./crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore'), 34 signedCert: path.join(__dirname,'./crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem'), 35 tls_cacerts:path.join(__dirname,'./crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt'), 36 server_hostname: "peer0.org2.example.com" 37 }; 38 39 var channel = {}; 40 var client = null; 41 const getKeyFilesInDir = (dir) => { 42 //該函數用於找到keystore目錄下的私鑰文件的路徑 43 var files = fs.readdirSync(dir) 44 var keyFiles = [] 45 files.forEach((file_name) => { 46 let filePath = path.join(dir, file_name) 47 if (file_name.endsWith('_sk')) { 48 keyFiles.push(filePath) 49 } 50 }) 51 return keyFiles 52 } 53 Promise.resolve().then(() => { 54 console.log("Load privateKey and signedCert"); 55 client = new hfc(); 56 var createUserOpt = { 57 username: options.user_id, 58 mspid: options.msp_id, 59 cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0], 60 signedCert: options.signedCert } 61 } 62 //以上代碼指定了當前用戶的私鑰,證書等基本信息 63 return sdkUtils.newKeyValueStore({ 64 path: "/tmp/fabric-client-stateStore/" 65 }).then((store) => { 66 client.setStateStore(store) 67 return client.createUser(createUserOpt) 68 }) 69 }).then((user) => { 70 channel = client.newChannel(options.channel_id); 71 72 let data = fs.readFileSync(options.tls_cacerts); 73 let peer = client.newPeer(options.network_url, 74 { 75 pem: Buffer.from(data).toString(), 76 'ssl-target-name-override': options.server_hostname 77 } 78 ); 79 peer.setName("peer0"); 80 //由於啓用了TLS,因此上面的代碼就是指定TLS的CA證書 81 channel.addPeer(peer); 82 return; 83 }).then(() => { 84 console.log("Make query"); 85 var transaction_id = client.newTransactionID(); 86 console.log("Assigning transaction_id: ", transaction_id._transaction_id); 87 //構造查詢request參數 88 if(arg.func=="queryPost"){ 89 const request = { 90 chaincodeId: options.chaincode_id, 91 txId: transaction_id, 92 fcn: 'queryPost', 93 args: ["POST"+arg.id] 94 }; 95 }else if(arg.func=="richQueryPosts"){ 96 const request = { 97 chaincodeId: options.chaincode_id, 98 txId: transaction_id, 99 fcn: 'richQueryPosts', 100 args: [arg.attribute,arg.operator,arg.value] 101 }; 102 }else if(arg.func=="getPostNum"){ 103 const request = { 104 chaincodeId: options.chaincode_id, 105 txId: transaction_id, 106 fcn: 'getPostNum', 107 args: [arg.attribute,arg.operator,arg.value] 108 }; 109 } 110 return channel.queryByChaincode(request); 111 }).then((query_responses) => { 112 console.log("returned from query"); 113 if (!query_responses.length) { 114 console.log("No payloads were returned from query"); 115 } else { 116 console.log("Query result count = ", query_responses.length) 117 } 118 if (query_responses[0] instanceof Error) { 119 console.error("error from query = ", query_responses[0]); 120 } 121 res.writeHead(200, {'Content-Type': 'text/plain'}); 122 res.end(query_responses[0]); 123 console.log("Response is ", query_responses[0].toString());//打印返回的結果 124 }).catch((err) => { 125 console.error("Caught Error", err); 126 }); 127 } 128 129 130 131 132 133 134 function invoke(arg){ 135 'use strict'; 136 137 var hfc = require('fabric-client'); 138 var path = require('path'); 139 var util = require('util'); 140 var sdkUtils = require('fabric-client/lib/utils') 141 const fs = require('fs'); 142 var options = { 143 user_id: 'Admin@org2.example.com', 144 msp_id:'Org2MSP', 145 channel_id: 'mychannel', 146 chaincode_id: 'mycc', 147 peer_url: 'grpcs://10.0.2.12:7051',//由於啓用了TLS,因此是grpcs,若是沒有啓用TLS,那麼就是grpc 148 event_url: 'grpcs://10.0.2.12:7053',//由於啓用了TLS,因此是grpcs,若是沒有啓用TLS,那麼就是grpc 149 orderer_url: 'grpcs://10.0.2.10:7050',//由於啓用了TLS,因此是grpcs,若是沒有啓用TLS,那麼就是grpc 150 privateKeyFolder: path.join(__dirname,'./crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore'), 151 signedCert:path.join(__dirname,'./crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem'), 152 peer_tls_cacerts: path.join(__dirname,'./crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt'), 153 orderer_tls_cacerts:path.join(__dirname,'./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt'), 154 server_hostname: "peer0.org2.example.com" 155 }; 156 157 var channel = {}; 158 var client = null; 159 var targets = []; 160 var tx_id = null; 161 const getKeyFilesInDir = (dir) => { 162 //該函數用於找到keystore目錄下的私鑰文件的路徑 163 const files = fs.readdirSync(dir) 164 const keyFiles = [] 165 files.forEach((file_name) => { 166 let filePath = path.join(dir, file_name) 167 if (file_name.endsWith('_sk')) { 168 keyFiles.push(filePath) 169 } 170 }) 171 return keyFiles 172 } 173 Promise.resolve().then(() => { 174 console.log("Load privateKey and signedCert"); 175 client = new hfc(); 176 var createUserOpt = { 177 username: options.user_id, 178 mspid: options.msp_id, 179 cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0], 180 signedCert: options.signedCert } 181 } 182 //以上代碼指定了當前用戶的私鑰,證書等基本信息 183 return sdkUtils.newKeyValueStore({ 184 path: "/tmp/fabric-client-stateStore/" 185 }).then((store) => { 186 client.setStateStore(store) 187 return client.createUser(createUserOpt) 188 }) 189 }).then((user) => { 190 channel = client.newChannel(options.channel_id); 191 let data = fs.readFileSync(options.peer_tls_cacerts); 192 let peer = client.newPeer(options.peer_url, 193 { 194 pem: Buffer.from(data).toString(), 195 'ssl-target-name-override': options.server_hostname 196 } 197 ); 198 //由於啓用了TLS,因此上面的代碼就是指定Peer的TLS的CA證書 199 channel.addPeer(peer); 200 //接下來鏈接Orderer的時候也啓用了TLS,也是一樣的處理方法 201 let odata = fs.readFileSync(options.orderer_tls_cacerts); 202 let caroots = Buffer.from(odata).toString(); 203 var orderer = client.newOrderer(options.orderer_url, { 204 'pem': caroots, 205 'ssl-target-name-override': "orderer.example.com" 206 }); 207 208 channel.addOrderer(orderer); 209 targets.push(peer); 210 return; 211 }).then(() => { 212 tx_id = client.newTransactionID(); 213 console.log("Assigning transaction_id: ", tx_id._transaction_id); 214 if(arg.func=="addPost"){ 215 var request = { 216 targets: targets, 217 chaincodeId: options.chaincode_id, 218 fcn: 'addPost', 219 args: [arg.originalwebsite,arg.originalid,arg.title,arg.content,arg.authorid,arg.publishtime,arg.updatetime,arg.category,arg.sourceid,arg.labels,arg.follower_num,arg.browse_num,arg.star_num], 220 chainId: options.channel_id, 221 txId: tx_id 222 }; 223 }else if(arg.func=="updatePost"){ 224 var request = { 225 targets: targets, 226 chaincodeId: options.chaincode_id, 227 fcn: 'updatePost', 228 args: [arg.id,arg.originalwebsite,arg.originalid,arg.title,arg.content,arg.authorid,arg.publishtime,arg.updatetime,arg.category,arg.sourceid,arg.labels,arg.follower_num,arg.browse_num,arg.star_num], 229 chainId: options.channel_id, 230 txId: tx_id 231 }; 232 } 233 return channel.sendTransactionProposal(request); 234 }).then((results) => { 235 var proposalResponses = results[0]; 236 var proposal = results[1]; 237 var header = results[2]; 238 let isProposalGood = false; 239 if (proposalResponses && proposalResponses[0].response && 240 proposalResponses[0].response.status === 200) { 241 isProposalGood = true; 242 console.log('transaction proposal was good'); 243 } else { 244 console.error('transaction proposal was bad'); 245 } 246 if (isProposalGood) { 247 console.log(util.format( 248 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', 249 proposalResponses[0].response.status, proposalResponses[0].response.message, 250 proposalResponses[0].response.payload, proposalResponses[0].endorsement.signature)); 251 console.log(proposalResponses[0].response.payload.toString()); 252 res.writeHead(200, {'Content-Type': 'text/plain'}); 253 res.end(proposalResponses[0].response.payload.toString()); 254 var request = { 255 proposalResponses: proposalResponses, 256 proposal: proposal, 257 header: header 258 }; 259 // set the transaction listener and set a timeout of 30sec 260 // if the transaction did not get committed within the timeout period, 261 // fail the test 262 var transactionID = tx_id.getTransactionID(); 263 var eventPromises = []; 264 let eh = client.newEventHub(); 265 //接下來設置EventHub,用於監聽Transaction是否成功寫入,這裏也是啓用了TLS 266 let data = fs.readFileSync(options.peer_tls_cacerts); 267 let grpcOpts = { 268 pem: Buffer.from(data).toString(), 269 'ssl-target-name-override': options.server_hostname 270 } 271 eh.setPeerAddr(options.event_url,grpcOpts); 272 eh.connect(); 273 274 let txPromise = new Promise((resolve, reject) => { 275 let handle = setTimeout(() => { 276 eh.disconnect(); 277 reject(); 278 }, 30000); 279 //向EventHub註冊事件的處理辦法 280 eh.registerTxEvent(transactionID, (tx, code) => { 281 clearTimeout(handle); 282 eh.unregisterTxEvent(transactionID); 283 eh.disconnect(); 284 285 if (code !== 'VALID') { 286 console.error( 287 'The transaction was invalid, code = ' + code); 288 reject(); 289 } else { 290 console.log( 291 'The transaction has been committed on peer ' + 292 eh._ep._endpoint.addr); 293 resolve(); 294 } 295 }); 296 }); 297 eventPromises.push(txPromise); 298 var sendPromise = channel.sendTransaction(request); 299 return Promise.all([sendPromise].concat(eventPromises)).then((results) => { 300 console.log(' event promise all complete and testing complete'); 301 return results[0]; // the first returned value is from the 'sendPromise' which is from the 'sendTransaction()' call 302 }).catch((err) => { 303 console.error( 304 'Failed to send transaction and get notifications within the timeout period.' 305 ); 306 return 'Failed to send transaction and get notifications within the timeout period.'; 307 }); 308 } else { 309 console.error( 310 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...' 311 ); 312 return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'; 313 } 314 }, (err) => { 315 console.error('Failed to send proposal due to error: ' + err.stack ? err.stack : 316 err); 317 return 'Failed to send proposal due to error: ' + err.stack ? err.stack : 318 err; 319 }).then((response) => { 320 if (response.status === 'SUCCESS') { 321 console.log('Successfully sent transaction to the orderer.'); 322 return tx_id.getTransactionID(); 323 } else { 324 console.error('Failed to order the transaction. Error code: ' + response.status); 325 return 'Failed to order the transaction. Error code: ' + response.status; 326 } 327 }, (err) => { 328 console.error('Failed to send transaction due to error: ' + err.stack ? err 329 .stack : err); 330 return 'Failed to send transaction due to error: ' + err.stack ? err.stack : 331 err; 332 }); 333 }
Docker會依照Dockerfile的內容來構建一個鏡像。
cd ..
touch Dockerfile
vi Dockerfile
#設置基礎鏡像,若是本地沒有該鏡像,會從Docker.io服務器pull鏡像
FROM node:6.11.3
#建立app目錄,保存咱們的代碼
RUN mkdir -p /usr/src/node
#設置工做目錄
WORKDIR /usr/src/node
#複製全部文件到 工做目錄。
COPY . /usr/src/node
#編譯運行node項目,使用npm安裝程序的全部依賴,利用taobao的npm安裝
WORKDIR /usr/src/node/mynodeapp
RUN npm install --registry=https://registry.npm.taobao.org
#暴露container的端口
EXPOSE 8888
#運行命令
CMD ["node", "server.js"]
在Dockerfile文件所在目錄下,運行下面命令來構建一個Image
sudo docker build -t fabric/node .
最後提示
Removing intetmediate container <臨時容器ID>
Successfully built <鏡像ID>
就算構建成功了。(這一步時常失敗,要看網絡情況)
構建完後查看一下剛構建的鏡像:
sudo docker images
多出來了一個fabric/node鏡像。
sudo docker run -d --name nodeapp -p 8888:8888 fabric/node:latest
-d 表示容器在後臺運行
--name 表示給容器別名 nodeapp
-p 表示端口映射。把本機的8888端口映射到容器的8888端口,這樣外網就能經過本機的8888端口,訪問咱們的web了。
後面的 fabric/node 是image的REPOSITORY, latest的鏡像的TAG
我使用python寫了一個簡單的發送和接受http請求的程序,固然用插件發送請求來測試會更加便捷(推薦使用postman,十分便捷)。代碼以下:
import urllib.request url = "http://localhost:8888/select?func=richQueryPosts&attribute=title&operator=0&value=d" req = urllib.request.Request(url) print(req) res_data = urllib.request.urlopen(req) res = res_data.read() print (res)
成功接收到包含對應帖子信息的response就說明node部署成功了。
以後對應的社區就能夠調用HTTP API來完成社區和區塊鏈的對接了!整個基於hyperledger fabric的社區聯盟開發的內容就到這裏了。