這篇文章主要講解了“比原的Dashboard怎么創(chuàng)建”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“比原的Dashboard怎么創(chuàng)建”吧!
創(chuàng)新互聯(lián)建站是一家專業(yè)提供懷安企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、成都h5網(wǎng)站建設(shè)、小程序制作等業(yè)務(wù)。10年已為懷安眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)公司優(yōu)惠進(jìn)行中。
當(dāng)我們使用bytomd node
啟動(dòng)比原節(jié)點(diǎn)的時(shí)候,不需要任何配置,它就會(huì)自動(dòng)啟用Dashboard功能,并且會(huì)在瀏覽器中打開頁面,非常方便。
如果是第一次運(yùn)行,還沒有創(chuàng)建過帳戶,它會(huì)提示我們創(chuàng)建一個(gè)帳戶及相關(guān)的私鑰:
我們可以通過填寫帳戶別名、密鑰別名和相應(yīng)的密碼來創(chuàng)建,或者點(diǎn)擊下面的"Restore wallet"來恢復(fù)之前的帳號(hào)(如果之前備份過的話):
點(diǎn)擊"Register"后,就會(huì)創(chuàng)建成功,并進(jìn)入管理頁面:
注意它的地址是:http://127.0.0.1:9888/dashboard
如果我們查看配置文件config.toml
,可以在其中看到它的身影:
fast_sync = true db_backend = "leveldb" api_addr = "0.0.0.0:9888" chain_id = "solonet" [p2p] laddr = "tcp://0.0.0.0:46658" seeds = ""
注意其中的api_addr
,就是dashboard以及web-api的地址。比原在啟動(dòng)之后,其BaseConfig.ApiAddress
會(huì)從配置文件中取到相應(yīng)的值:
config/config.go#L41-L85
type BaseConfig struct { // ... ApiAddress string `mapstructure:"api_addr"` // ... }
然后在啟動(dòng)時(shí),比原的web api以及dashboard會(huì)使用該地址,并且在瀏覽器中打開dashboard。
然而此處有一個(gè)奇怪的問題,就是不論這里的值是什么,瀏覽器總是打開http://localhost:9888
這個(gè)地址。為什么呢?因?yàn)樗鼘懰涝诹舜a中。
在代碼中,http://localhost:9888
一共出現(xiàn)在了三個(gè)地方,一個(gè)是用來表示dashboard的訪問地址,位于node/node.go
中:
node/node.go#L33-L37
const ( webAddress = "http://127.0.0.1:9888" expireReservationsPeriod = time.Second maxNewBlockChSize = 1024 )
這里的webAddress
,只在從代碼中打開瀏覽器顯示dashboard時(shí)使用:
node/node.go#L153-L159
func lanchWebBroser() { log.Info("Launching System Browser with :", webAddress) if err := browser.Open(webAddress); err != nil { log.Error(err.Error()) return } }
比原通過"github.com/toqueteos/webbrowser"
這個(gè)第三方的庫,可以在節(jié)點(diǎn)啟動(dòng)的時(shí)候,調(diào)用系統(tǒng)默認(rèn)的瀏覽器,并打開指定的網(wǎng)址,方便了用戶。(注意這段代碼中有不少錯(cuò)別字,比如lanch
、broser
,已在后續(xù)版本中修正了)
另一個(gè)地方,是用于bytomcli
這個(gè)命令行工具的,只是奇怪的是它放在了util/util.go
下面:
util/util.go#L26-L28
var ( coreURL = env.String("BYTOM_URL", "http://localhost:9888") )
為什么說它是屬于bytomcli
的呢?因?yàn)檫@個(gè)coreURL
最終被用在util
包下的一個(gè)ClientCall(...)
函數(shù)中,用于從代碼中向指定的web api發(fā)送請求,并使用其回復(fù)信息。但是這個(gè)方法在bytomcli
所在的包使用。如果是這樣的話,coreURL
及相關(guān)的函數(shù),應(yīng)該移到bytomcli
包里才對(duì)。
第三個(gè)地方,跟第二個(gè)非常像,但是位于tools/sendbulktx/core/util.go
中,它是用于另一個(gè)命令行工具sendbulktx
的:
tools/sendbulktx/core/util.go#L26-L28
var ( coreURL = env.String("BYTOM_URL", "http://localhost:9888") )
一模一樣,對(duì)吧。其實(shí)不光是這里,還有一堆相關(guān)的方法和函數(shù),也是一模一樣的,一看就是跟第二處互相復(fù)制過來的。
關(guān)于這里的問題,我提了兩個(gè)issue:
dashboard和web api的地址寫在配置文件config.toml中,但是同時(shí)寫死在代碼中:這里在實(shí)現(xiàn)上的確是有一定難度的,原因是在配置文件中,寫的是0.0.0.0:9998
,但是從瀏覽器或者命令行工具中去訪問時(shí),需要使用一個(gè)具體的ip(而不是0.0.0.0
),否則某些功能會(huì)不正常。另外,在后面的代碼分析處會(huì)看到,除了配置文件中的這個(gè)地址,比原還會(huì)優(yōu)先從環(huán)境變量中取得LISTEN
所對(duì)應(yīng)的地址web api的地址。所以這里需要更多的研究才能正確修復(fù)。
與讀取webapi相關(guān)的代碼出現(xiàn)大量重復(fù):官方解釋說sendbulktx
這個(gè)工具在未來將從bytom項(xiàng)目中獨(dú)立出去,所以代碼是重復(fù)的,如果是這樣的話,可以接受。
下面我們快速過一遍比原的Dashboard提供了哪些信息和功能。由于在本文中,我們關(guān)注的重點(diǎn)不是這些具體的功能,所以會(huì)不會(huì)細(xì)究。另外,前面剛創(chuàng)建好的帳號(hào)里,很多數(shù)據(jù)都是沒有的,為了展示方便,我事先做了一些數(shù)據(jù)。
首先是密鑰:
這里顯示了當(dāng)前有幾個(gè)密鑰,其別名是什么,并且顯示出來了主公鑰。我們可以點(diǎn)擊右上角的“新建”按鈕創(chuàng)建多個(gè)密鑰,但是這里不再展示。
帳戶:
資產(chǎn):
默認(rèn)只定義了BTM
這一種資產(chǎn),可以通過“新建”按鈕增加多種資產(chǎn)。
余額:
看起來我還是相當(dāng)有錢的(可惜不能用)。
交易:
展示了多筆交易,實(shí)際上是在本機(jī)挖礦挖出來的。由于挖礦出來的BTM是由系統(tǒng)直接轉(zhuǎn)到我們的帳戶上的,所以也可以看作是一種交易。
創(chuàng)建交易:
我們也可以像這樣自己創(chuàng)建交易,把我們持有的某種資產(chǎn)(比如BTM)轉(zhuǎn)到另一個(gè)地址。
未花費(fèi)輸出:
簡單的理解就是與我相關(guān)的每一筆交易都被記錄下來,有輸入和輸出部分,其中的輸出可能又是另一個(gè)交易的輸入。這里顯示的是還沒有花費(fèi)掉的輸出(可以根據(jù)它來計(jì)算我當(dāng)前到底還剩下多少余額)
查看核心狀態(tài):
定義訪問控制:
備份和還原操作:
另外每個(gè)頁面左側(cè)欄的下面,還有關(guān)于連接的鏈的類型(此處為solonet
),以及同步情況和與當(dāng)前節(jié)點(diǎn)連接的其它節(jié)點(diǎn)數(shù)。
這里展示的信息和功能我們還不需要細(xì)究,但是這里出現(xiàn)的名詞卻是要留意的,因?yàn)樗鼈兌际潜仍暮诵母拍?。等我們以后研究比原?nèi)部區(qū)塊鏈核心功能的時(shí)候,實(shí)際上都是圍繞著它們來的。這里的每一個(gè)概念,可能都需要一到多篇文章專門討論。
我們在今天關(guān)注的是技術(shù)實(shí)現(xiàn)層面,下面我們要開始進(jìn)入代碼時(shí)間了。
首先讓我們從比原節(jié)點(diǎn)啟動(dòng)開始,一直找到啟動(dòng)http服務(wù)的地方:
cmd/bytomd/main.go#L54-L57
func main() { cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir())) cmd.Execute() }
cmd/bytomd/commands/run_node.go#L41-L54
func runNode(cmd *cobra.Command, args []string) error { // Create & start node n := node.NewNode(config) if _, err := n.Start(); err != nil { // .. }
node/node.go#L169-L180
func (n *Node) OnStart() error { // ... n.initAndstartApiServer() // ... }
很快找到了,initAndstartApiServer
:
node/node.go#L161-L167
func (n *Node) initAndstartApiServer() { // 1. n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens) // 2. listenAddr := env.String("LISTEN", n.config.ApiAddress) env.Parse() // 3. n.api.StartServer(*listenAddr) }
可以看到,該方法分成了三部分:
通過傳入大量的參數(shù),來構(gòu)造一個(gè)API
對(duì)象。進(jìn)去后會(huì)看到大量的與url相關(guān)的配置。
先從環(huán)境中取得LISTEN
對(duì)應(yīng)的值,如果沒有的話,再使用config.toml
中指定的api_addr
值,作為api服務(wù)的入口地址
真正啟動(dòng)服務(wù)
由于2比較簡單,所以我們下面將仔細(xì)分析1和3.
先找到1處所對(duì)應(yīng)的api.NewAPI
方法:
api/api.go#L143-L157
func NewAPI(sync *netsync.SyncManager, wallet *wallet.Wallet, txfeeds *txfeed.Tracker, cpuMiner *cpuminer.CPUMiner, miningPool *miningpool.MiningPool, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore) *API { api := &API{ sync: sync, wallet: wallet, chain: chain, accessTokens: token, txFeedTracker: txfeeds, cpuMiner: cpuMiner, miningPool: miningPool, } api.buildHandler() api.initServer(config) return api }
它主要就是把傳進(jìn)來的各參數(shù)拿住,供后面使用。然后就是api.buildHandler
來配置各個(gè)功能點(diǎn)的路徑和處理函數(shù),以及用api.initServer
來初始化服務(wù)。
進(jìn)入api.buildHandler()
。這個(gè)方法有點(diǎn)長,把它分成幾部分來講解:
api/api.go#L164-L244
func (a *API) buildHandler() { walletEnable := false m := http.NewServeMux()
看來http服務(wù)使用的是Go自帶的http
包。
向下是,當(dāng)用戶的錢包功能沒有禁用的話,就會(huì)配置與錢包相關(guān)的各功能點(diǎn)(比如帳號(hào)、交易、密鑰等):
if a.wallet != nil { walletEnable = true m.Handle("/create-account", jsonHandler(a.createAccount)) m.Handle("/list-accounts", jsonHandler(a.listAccounts)) m.Handle("/delete-account", jsonHandler(a.deleteAccount)) m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver)) m.Handle("/list-addresses", jsonHandler(a.listAddresses)) m.Handle("/validate-address", jsonHandler(a.validateAddress)) m.Handle("/create-asset", jsonHandler(a.createAsset)) m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias)) m.Handle("/get-asset", jsonHandler(a.getAsset)) m.Handle("/list-assets", jsonHandler(a.listAssets)) m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey)) m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys)) m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey)) m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword)) m.Handle("/build-transaction", jsonHandler(a.build)) m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates)) m.Handle("/submit-transaction", jsonHandler(a.submit)) m.Handle("/estimate-transaction-gas", jsonHandler(a.estimateTxGas)) m.Handle("/get-transaction", jsonHandler(a.getTransaction)) m.Handle("/list-transactions", jsonHandler(a.listTransactions)) m.Handle("/list-balances", jsonHandler(a.listBalances)) m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs)) m.Handle("/backup-wallet", jsonHandler(a.backupWalletImage)) m.Handle("/restore-wallet", jsonHandler(a.restoreWalletImage)) } else { log.Warn("Please enable wallet") }
錢包功能默認(rèn)是啟用的,用戶如何才能禁用它呢?方法是在配置文件config.toml
中,加上這一節(jié)代碼:
[wallet] disable = true
在前面的代碼中,在配置功能點(diǎn)時(shí),使用了大量的m.Handle("/create-account", jsonHandler(a.createAccount))
這樣的代碼,它是什么意思呢?
/create-account
:該功能的路徑,比如對(duì)于這個(gè),用戶需要在瀏覽器或者命令行中,使用地址http://localhost:9888/create-account
來訪問
a.createAccount
:用于處理用戶的訪問,比如拿到用戶提供的數(shù)據(jù),處理完后再返回某個(gè)數(shù)據(jù)給用戶,會(huì)在下面詳解
jsonHandler
:是一個(gè)中間層,把用戶發(fā)送的JSON數(shù)據(jù)轉(zhuǎn)成第2步handler需要的Go類型參數(shù),或者把2返回的Go數(shù)據(jù)轉(zhuǎn)成JSON給用戶
m.Handle(path, handler)
:用來把功能點(diǎn)路徑和相應(yīng)的處理函數(shù)對(duì)應(yīng)起來
這里先看第3步中的jsonHandler
的代碼:
api/api.go#L259-L265
func jsonHandler(f interface{}) http.Handler { h, err := httpjson.Handler(f, errorFormatter.Write) if err != nil { panic(err) } return h }
它里面用到了httpjson
,它是比原代碼中提供的一個(gè)包,位于net/http/httpjson 。它的功能主要是為了在http訪問與Go的函數(shù)之間增加了一層轉(zhuǎn)換。通常用戶通過http與api交互的時(shí)候,發(fā)送和接收的都是JSON數(shù)據(jù),而我們在第2步的handler中定義的是Go函數(shù),通過httpjson
,可以在兩者之間自動(dòng)轉(zhuǎn)換,使得我們在寫Go代碼的時(shí)候,不需要考慮JSON以及http協(xié)議相關(guān)的問題。相應(yīng)的,為了與jsonhttp配合使用,第2步中的handler在格式上也會(huì)有一些要求,詳情可參見這里的詳細(xì)注釋:net/http/httpjson/doc.go#L3-L40 。由于httpjson所涉及的代碼還比較多,這里就不詳述,以后有機(jī)會(huì)專開一篇。
然后我們再看第2步的a.createAccount
的代碼:
api/accounts.go#L16-L30
func (a *API) createAccount(ctx context.Context, ins struct { RootXPubs []chainkd.XPub `json:"root_xpubs"` Quorum int `json:"quorum"` Alias string `json:"alias"` }) Response { acc, err := a.wallet.AccountMgr.Create(ctx, ins.RootXPubs, ins.Quorum, ins.Alias) if err != nil { return NewErrorResponse(err) } annotatedAccount := account.Annotated(acc) log.WithField("account ID", annotatedAccount.ID).Info("Created account") return NewSuccessResponse(annotatedAccount) }
這個(gè)函數(shù)的內(nèi)容我們在這里不細(xì)究,需要注意的反而是它的格式,因?yàn)榍懊嬲f了,它需要跟jsonHandler
配合使用。格式的要求大概就是,第一個(gè)參數(shù)是Context
,第二個(gè)參數(shù)是可以從JSON數(shù)據(jù)轉(zhuǎn)換過來的參數(shù),返回值是一個(gè)Response以及一個(gè)Error,但是這四個(gè)又全部是可選的。
讓我們回到api.buildHandler()
,繼續(xù)往下:
m.Handle("/", alwaysError(errors.New("not Found"))) m.Handle("/error", jsonHandler(a.walletError)) m.Handle("/create-access-token", jsonHandler(a.createAccessToken)) m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens)) m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken)) m.Handle("/check-access-token", jsonHandler(a.checkAccessToken)) m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed)) m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed)) m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed)) m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed)) m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds)) m.Handle("/get-unconfirmed-transaction", jsonHandler(a.getUnconfirmedTx)) m.Handle("/list-unconfirmed-transactions", jsonHandler(a.listUnconfirmedTxs)) m.Handle("/get-block-hash", jsonHandler(a.getBestBlockHash)) m.Handle("/get-block-header", jsonHandler(a.getBlockHeader)) m.Handle("/get-block", jsonHandler(a.getBlock)) m.Handle("/get-block-count", jsonHandler(a.getBlockCount)) m.Handle("/get-difficulty", jsonHandler(a.getDifficulty)) m.Handle("/get-hash-rate", jsonHandler(a.getHashRate)) m.Handle("/is-mining", jsonHandler(a.isMining)) m.Handle("/set-mining", jsonHandler(a.setMining)) m.Handle("/get-work", jsonHandler(a.getWork)) m.Handle("/submit-work", jsonHandler(a.submitWork)) m.Handle("/gas-rate", jsonHandler(a.gasRate)) m.Handle("/net-info", jsonHandler(a.getNetInfo))
可以看到還是各種功能的定義,主要是跟區(qū)塊數(shù)據(jù)、挖礦、訪問控制等相關(guān)的功能,這里就不詳述了。
再繼續(xù):
handler := latencyHandler(m, walletEnable) handler = maxBytesHandler(handler) handler = webAssetsHandler(handler) handler = gzip.Handler{Handler: handler} a.handler = handler }
這里是把前面定義的功能點(diǎn)配置包成了一個(gè)handler,然后在它外面包了一層又一層,添加上了更多的功能:
latencyHandler
:我目前還不能準(zhǔn)確說出它的作用,留待以后補(bǔ)充
maxBytesHandler
:防止用戶提交的數(shù)據(jù)過大,目前值約為10MB
。對(duì)于除signer/sign-block
以外的url有效
webAssetsHandler
:向用戶提供dashboard相關(guān)的前端頁面資源(比如網(wǎng)頁、圖片等等)??赡苁菫榱诵阅芎头奖阈苑矫娴目紤],前端文件都經(jīng)過混淆后,以字符串形式嵌入在dashboard/dashboard.go中,真正的代碼在另一個(gè)項(xiàng)目中 https://github.com/Bytom/dashboard,我們在后面會(huì)看一下
gzip.Handler
:對(duì)http客戶端進(jìn)行是否支持gzip
的檢測,并且在支持的情況下,傳輸數(shù)據(jù)時(shí)使用gzip壓縮
然后讓我們回到主線,看看前面的NewAPI
中最后調(diào)用的api.initServer(config)
:
api/api.go#L89-L122
func (a *API) initServer(config *cfg.Config) { // The waitHandler accepts incoming requests, but blocks until its underlying // handler is set, when the second phase is complete. var coreHandler waitHandler var handler http.Handler coreHandler.wg.Add(1) mux := http.NewServeMux() mux.Handle("/", &coreHandler) handler = mux if config.Auth.Disable == false { handler = AuthHandler(handler, a.accessTokens) } handler = RedirectHandler(handler) secureheader.DefaultConfig.PermitClearLoopback = true secureheader.DefaultConfig.HTTPSRedirect = false secureheader.DefaultConfig.Next = handler a.server = &http.Server{ // Note: we should not set TLSConfig here; // we took care of TLS with the listener in maybeUseTLS. Handler: secureheader.DefaultConfig, ReadTimeout: httpReadTimeout, WriteTimeout: httpWriteTimeout, // Disable HTTP/2 for now until the Go implementation is more stable. // https://github.com/golang/go/issues/16450 // https://github.com/golang/go/issues/17071 TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, } coreHandler.Set(a) }
這個(gè)方法在本文不適合細(xì)講,因?yàn)樗嗟氖巧婕暗絟ttp層面的一些東西,不是本文的重點(diǎn)。值得關(guān)注的地方是,方法創(chuàng)建了一個(gè)Go提供的http.Server
,把前面我們辛苦配置好的handler塞進(jìn)去,萬事俱備,只欠啟動(dòng)。
下面就是啟動(dòng)啦。我們終于可以回到最新的initAndstartApiServer
方法了,還記得它的第3塊內(nèi)容嗎?主要就是調(diào)用了n.api.StartServer(*listenAddr)
:
api/api.go#L125-L140
func (a *API) StartServer(address string) { // ... listener, err := net.Listen("tcp", address) // ... go func() { if err := a.server.Serve(listener); err != nil { log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server") } }() }
這塊比較簡單,就是使用Go的net.Listen
來監(jiān)聽傳入的web api地址,得到相應(yīng)的listener之后,把它傳給我們在前面創(chuàng)建的http.Server
的Serve
方法,就大功告成了。
這一塊代碼分析寫得十分痛苦,主要原因是它的web api這里幾乎涉及到了所有比原提供的功能,很龐雜。還有不少跟http協(xié)議相關(guān)的東西。同時(shí),因?yàn)楸┞冻隽私涌?,這里就容易出現(xiàn)安全風(fēng)險(xiǎn),所以代碼里面還有不少涉及到用戶輸入、安全檢查等。這些東西當(dāng)然是非常重要的,但是從代碼閱讀的角度上來講又難免枯燥,除非我們就是為了研究安全性。
本文的任務(wù)主要是研究比原是如何提供http服務(wù)的,關(guān)于比原在安全性方面做了哪些事情,以后會(huì)有專門的分析。
比原的前端代碼是在另一個(gè)獨(dú)立的項(xiàng)目中:https://github.com/Bytom/dashboard
本文我們并不去探討代碼細(xì)節(jié),而僅僅去看一下它使用了哪些前端框架,有個(gè)大概印象即可。
通過https://github.com/Bytom/dashboard/blob/master/package.json我們就可以大概了解到,比原前端使用了:
構(gòu)建工具:直接利用npm
的Scripts
前端框架:React
+ Redux
CSS方面:bootstrap
JavaScript:ES6
http請求:fetch-ponyfill
資源打包:webpack
測試:mocha
以Account相關(guān)的代碼為例:
src/sdk/api/accounts.js#L16
const accountsAPI = (client) => { return { create: (params, cb) => shared.create(client, '/create-account', params, {cb, skipArray: true}), createBatch: (params, cb) => shared.createBatch(client, '/create-account', params, {cb}), // ... listAddresses: (accountId) => shared.query(client, 'accounts', '/list-addresses', {account_id: accountId}), } }
這些函數(shù)主要是通過fetch-ponyfill
庫中提供的方法,向向前面使用go創(chuàng)建的web api接口發(fā)送http請求,并且拿到相應(yīng)的回復(fù)數(shù)據(jù)。而它們又將在React組件中被調(diào)用,拿回來的數(shù)據(jù)用于填充頁面。
感謝各位的閱讀,以上就是“比原的Dashboard怎么創(chuàng)建”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)比原的Dashboard怎么創(chuàng)建這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!