通过mux
定义了两个Handler,URL都是/
,但是对应的Method
是不一样的。
GET
方法通过handleGetBlockchain
函数实现,用于获取区块链的信息。
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))
}
Blockchain
是一个[]Block
,handleGetBlockchain
函数的作用是把Blockchain
格式化为JSON字符串,然后显示出来。io.WriteString
是一个很好用的函数,可以往Writer
里写入字符串。更多参考 Go语言实战笔记(十九)| Go Writer 和 Reader
'POST'方法通过handleWriteBlock
函数实现,用于模拟区块的生成。
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
//使用了一个Mesage结构体,更方便的存储BPM
var msg Message
//接收请求的数据信息,类似{"BPM":60}这样的格式
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&msg); err != nil {
respondWithJSON(w, r, http.StatusBadRequest, r.Body)
return
}
defer r.Body.Close()
//控制并发,生成区块链,并且校验
mutex.Lock()
prevBlock := Blockchain[len(Blockchain)-1]
newBlock := generateBlock(prevBlock, msg.BPM)
//校验区块链
if isBlockValid(newBlock, prevBlock) {
Blockchain = append(Blockchain, newBlock)
spew.Dump(Blockchain)
}
mutex.Unlock()
//返回新的区块信息
respondWithJSON(w, r, http.StatusCreated, newBlock)
}
以上代码我进行了注释,便于理解。主要是通过POST发送一个{"BPM":60}
格式的BODY来添加区块,如果格式正确,那么就生成区块进行校验,合格了就加入到区块里;如果格式不对,那么返回错误信息。
用于控制并发的锁可以参考Go语言实战笔记(十七)| Go 读写锁
这个方法里有个Message
结构体,主要是为了便于操作方便。
// Message takes incoming JSON payload for writing heart rate
type Message struct {
BPM int
}
返回的JSON信息,也被抽取成了一个函数respondWithJSON
,便于公用。
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)
}
好了,快完成了,以上Web的Handler已经好了,现在我们要启动我们的Web服务了。
代码语言:javascript复制// web server
func run() error {
mux := makeMuxRouter()
//从配置文件里读取监听的端口
httpPort := os.Getenv("PORT")
log.Println("HTTP Server Listening on port :", httpPort)
s := &http.Server{
Addr: ":" httpPort,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}
和原生的http.Server
基本一样,应该比较好理解。mux其实也是一个Handler,这就是整个Handler处理链。现在我们就差一个main
主函数来启动我们整个程序了。
//控制并发的锁
var mutex = &sync.Mutex{}
func main() {
//加载env配置文件
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
//开启一个goroutine生成一个创世区块
go func() {
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""}
spew.Dump(genesisBlock)
mutex.Lock()
Blockchain = append(Blockchain, genesisBlock)
mutex.Unlock()
}()
log.Fatal(run())
}
整个main
函数并不太复杂,主要就是加载env配置文件,开启一个go协程生成一个创世区块并且添加到区块链的第一个位置,然后就是通过run
函数启动Web服务。
一个区块链都有一个创世区块,也就是第一个区块。有了第一个区块我们才能添加第二个,第三个,第N个区块。创世区块因为是第一个区块,所以它是没有PrevHash
的。