搭建基於hyperledger fabric的聯盟社區(六) --搭建node.js服務器

接下來我要作的是用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

 

二.建立node.js程序

2.1建立 package.json,並寫入相關信息和依賴

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" 
     ] 
}

 2.2 建立server.js

將與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 }
View Code

 

三.建立Dockerfile

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"]

 

四.構建image

在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的社區聯盟開發的內容就到這裏了。

相關文章
相關標籤/搜索