溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么初始化web服務(wù)

發(fā)布時(shí)間:2021-12-24 16:54:01 來源:億速云 閱讀:113 作者:iii 欄目:互聯(lián)網(wǎng)科技

本篇內(nèi)容介紹了“怎么初始化web服務(wù)”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

開發(fā)環(huán)境

我們假設(shè)你已經(jīng)具備一點(diǎn) Go 語言的開發(fā)經(jīng)驗(yàn)。在安裝和配置 Go 開發(fā)環(huán)境后之后,我們還要獲取以下一些依賴:

~$ go get github.com/davecgh/go-spew/spew

spew可以幫助我們在終端中中直接查看 struct 和 slice 這兩種數(shù)據(jù)結(jié)構(gòu)。

~$ go get github.com/gorilla/mux

Gorilla 的 mux 包非常流行, 我們用它來寫 web handler。

~$ go get github.com/joho/godotenv

godotenv 可以幫助我們讀取項(xiàng)目根目錄中的.env 配置文件,這樣就不用將 http端口之類的配置硬編碼進(jìn)代碼中了。比如像這樣:

ADDR=8080

接下來,我們創(chuàng)建一個(gè) main.go 文件。之后的大部分工作都圍繞這個(gè)文件,開始寫代碼吧!

導(dǎo)入依賴包

我們將所有的依賴包以聲明的方式導(dǎo)入進(jìn)去:

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "io"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/davecgh/go-spew/spew"
    "github.com/gorilla/mux"
    "github.com/joho/godotenv"
)

數(shù)據(jù)模型

接著我們來定義一個(gè)結(jié)構(gòu)體,它代表組成區(qū)塊鏈的每一個(gè)塊的數(shù)據(jù)模型:

type Block struct {
    Index     int
    Timestamp string
    BPM       int
    Hash      string
    PrevHash  string
}
  • Index 是這個(gè)塊在整個(gè)鏈中的位置

  • Timestamp 顯而易見就是塊生成時(shí)的時(shí)間戳

  • Hash 是這個(gè)塊通過 SHA256 算法生成的散列值

  • PrevHash 代表前一個(gè)塊的 SHA256 散列值

  • BPM 每分鐘心跳數(shù),也就是心率

接著,我們再定義一個(gè)結(jié)構(gòu)表示整個(gè)鏈,最簡單的表示形式就是一個(gè) Block 的 slice:

var Blockchain []Block

我們使用散列算法(SHA256)來確定和維護(hù)鏈中塊和塊正確的順序,確保每一個(gè)塊的 PrevHash 值等于前一個(gè)塊中的 Hash 值,這樣就以正確的塊順序構(gòu)建出鏈:

怎么初始化web服務(wù)

散列和生成新塊

我們?yōu)槭裁葱枰⒘??主要是兩個(gè)原因:

  • 在節(jié)省空間的前提下去唯一標(biāo)識數(shù)據(jù)。散列是用整個(gè)塊的數(shù)據(jù)計(jì)算得出,在我們的例子中,將整個(gè)塊的數(shù)據(jù)通過 SHA256 計(jì)算成一個(gè)定長不可偽造的字符串。

  • 維持鏈的完整性。通過存儲(chǔ)前一個(gè)塊的散列值,我們就能夠確保每個(gè)塊在鏈中的正確順序。任何對數(shù)據(jù)的篡改都將改變散列值,同時(shí)也就破壞了鏈。以我們從事的醫(yī)療健康領(lǐng)域?yàn)槔?,比如有一個(gè)惡意的第三方為了調(diào)整“人壽險(xiǎn)”的價(jià)格,而修改了一個(gè)或若干個(gè)塊中的代表不健康的 BPM 值,那么整個(gè)鏈都變得不可信了。

我們接著寫一個(gè)函數(shù),用來計(jì)算給定的數(shù)據(jù)的 SHA256 散列值:

func calculateHash(block Block) string {
    record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
    h := sha256.New()
    h.Write([]byte(record))
    hashed := h.Sum(nil)
    return hex.EncodeToString(hashed)
}

這個(gè) calculateHash 函數(shù)接受一個(gè)塊,通過塊中的 Index,Timestamp,BPM,以及 PrevHash 值來計(jì)算出 SHA256 散列值。接下來我們就能編寫一個(gè)生成塊的函數(shù):

func generateBlock(oldBlock Block, BPM int) (Block, error) {
    var newBlock Block

    t := time.Now()
    newBlock.Index = oldBlock.Index + 1
    newBlock.Timestamp = t.String()
    newBlock.BPM = BPM
    newBlock.PrevHash = oldBlock.Hash
    newBlock.Hash = calculateHash(newBlock)

    return newBlock, nil
}

其中,Index 是從給定的前一塊的 Index 遞增得出,時(shí)間戳是直接通過 time.Now() 函數(shù)來獲得的,Hash 值通過前面的 calculateHash 函數(shù)計(jì)算得出,PrevHash 則是給定的前一個(gè)塊的 Hash 值。

校驗(yàn)塊

搞定了塊的生成,接下來我們需要有函數(shù)幫我們判斷一個(gè)塊是否有被篡改。檢查 Index 來看這個(gè)塊是否正確得遞增,檢查 PrevHash 與前一個(gè)塊的 Hash 是否一致,再來通過 calculateHash 檢查當(dāng)前塊的 Hash 值是否正確。通過這幾步我們就能寫出一個(gè)校驗(yàn)函數(shù):

func isBlockValid(newBlock, oldBlock Block) bool {
    if oldBlock.Index+1 != newBlock.Index {
        return false
    }
    if oldBlock.Hash != newBlock.PrevHash {
        return false
    }
    if calculateHash(newBlock) != newBlock.Hash {
        return false
    }
    return true
}

除了校驗(yàn)塊以外,我們還會(huì)遇到一個(gè)問題:兩個(gè)節(jié)點(diǎn)都生成塊并添加到各自的鏈上,那我們應(yīng)該以誰為準(zhǔn)?這里的細(xì)節(jié)我們留到下一篇文章, 這里先讓我們記住一個(gè)原則:始終選擇最長的鏈:

怎么初始化web服務(wù)

通常來說,更長的鏈表示它的數(shù)據(jù)(狀態(tài))是更新的,所以我們需要一個(gè)函數(shù)能幫我們將本地的過期的鏈切換成最新的鏈:

func replaceChain(newBlocks []Block) {
    if len(newBlocks) > len(Blockchain) {
        Blockchain = newBlocks
    }
}

到這一步,我們基本就把所有重要的函數(shù)完成了。接下來,我們需要一個(gè)方便直觀的方式來查看我們的鏈,包括數(shù)據(jù)及狀態(tài)。通過瀏覽器查看 web 頁面可能是最合適的方式!

Web 服務(wù)

我猜你一定對傳統(tǒng)的 web 服務(wù)及開發(fā)非常熟悉,所以這部分你肯定一看就會(huì)。

借助 Gorilla/mux 包,我們先寫一個(gè)函數(shù)來初始化我們的 web 服務(wù):

func run() error {
    mux := makeMuxRouter()
    httpAddr := os.Getenv("ADDR")
    log.Println("Listening on ", os.Getenv("ADDR"))
    s := &http.Server{
        Addr:           ":" + httpAddr,
        Handler:        mux,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    if err := s.ListenAndServe(); err != nil {
        return err
    }

    return nil
}

其中的端口號是通過前面提到的 .env 來獲得,再添加一些基本的配置參數(shù),這個(gè) web 服務(wù)就已經(jīng)可以 listen and serve 了!

接下來我們再來定義不同 endpoint 以及對應(yīng)的 handler。例如,對“/”的 GET 請求我們可以查看整個(gè)鏈,“/”的 POST 請求可以創(chuàng)建塊。

func makeMuxRouter() http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
    return muxRouter
}

GET 請求的 handler:

func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
    bytes, err := json.MarshalIndent(Blockchain, "", "  ")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    io.WriteString(w, string(bytes))
}

為了簡化,我們直接以 JSON 格式返回整個(gè)鏈,你可以在瀏覽器中訪問 localhost:8080 或者 127.0.0.1:8080 來查看(這里的8080就是你在 .env 中定義的端口號 ADDR)。

POST 請求的 handler 稍微有些復(fù)雜,我們先來定義一下 POST 請求的 payload:

type Message struct {
    BPM int
}

再看看 handler 的實(shí)現(xiàn):

func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
    var m Message

    decoder := json.NewDecoder(r.Body)
    if err := decoder.Decode(&m); err != nil {
        respondWithJSON(w, r, http.StatusBadRequest, r.Body)
        return
    }
    defer r.Body.Close()

    newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
    if err != nil {
        respondWithJSON(w, r, http.StatusInternalServerError, m)
        return
    }
    if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
        newBlockchain := append(Blockchain, newBlock)
        replaceChain(newBlockchain)
        spew.Dump(Blockchain)
    }

    respondWithJSON(w, r, http.StatusCreated, newBlock)

}

我們的 POST 請求體中可以使用上面定義的 payload,比如:

{"BPM":75}

還記得前面我們寫的 generateBlock 這個(gè)函數(shù)嗎?它接受一個(gè)“前一個(gè)塊”參數(shù),和一個(gè) BPM 值。POST handler 接受請求后就能獲得請求體中的 BPM 值,接著借助生成塊的函數(shù)以及校驗(yàn)塊的函數(shù)就能生成一個(gè)新的塊了!

除此之外,你也可以:

  • 使用spew.Dump 這個(gè)函數(shù)可以以非常美觀和方便閱讀的方式將 struct、slice 等數(shù)據(jù)打印在控制臺(tái)里,方便我們調(diào)試。

  • 測試 POST 請求時(shí),可以使用 POSTMAN 這個(gè) chrome 插件,相比 curl它更直觀和方便。

POST 請求處理完之后,無論創(chuàng)建塊成功與否,我們需要返回客戶端一個(gè)響應(yīng):

func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
    response, err := json.MarshalIndent(payload, "", "  ")
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("HTTP 500: Internal Server Error"))
        return
    }
    w.WriteHeader(code)
    w.Write(response)
}

快要大功告成了。

接下來,我們把這些關(guān)于區(qū)塊鏈的函數(shù),web 服務(wù)的函數(shù)“組裝”起來:

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }

    go func() {
        t := time.Now()
        genesisBlock := Block{0, t.String(), 0, "", ""}
        spew.Dump(genesisBlock)
        Blockchain = append(Blockchain, genesisBlock)
    }()
    log.Fatal(run())
}

這里的 genesisBlock (創(chuàng)世塊)是 main 函數(shù)中最重要的部分,通過它來初始化區(qū)塊鏈,畢竟第一個(gè)塊的 PrevHash 是空的。

哦耶!完成了

可以從這里獲得完整的代碼:Github repo

讓我們來啟動(dòng)它:

~$ go run main.go

在終端中,我們可以看到 web 服務(wù)器啟動(dòng)的日志信息,并且打印出了創(chuàng)世塊的信息:

怎么初始化web服務(wù)

接著我們打開瀏覽器,訪問 localhost:8080 這個(gè)地址,我們可以看到頁面中展示了當(dāng)前整個(gè)區(qū)塊鏈的信息(當(dāng)然,目前只有一個(gè)創(chuàng)世塊):

怎么初始化web服務(wù)

接著,我們再通過 POSTMAN 來發(fā)送一些 POST 請求:

怎么初始化web服務(wù) 刷新剛才的頁面,現(xiàn)在的鏈中多了一些塊,正是我們剛才生成的,同時(shí)你們可以看到,塊的順序和散列值都正確。

怎么初始化web服務(wù)

“怎么初始化web服務(wù)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

web
AI