使用闭包进行状态处理
发布者:admin 发表于:439天前 阅读数:612 评论:0

将状态传递给处理程序通常很棘手。有两种方法:通过闭包传递状态,这有助于提高单个处理程序的灵活性,或使用结构体进行传递。

我们将使用结构控制器来存储接口,并使用由外部函数修改的单个处理程序创建两个路由。

实践

建立 controller.go:

package controllers

// Controller 传递状态给处理函数
type Controller struct {
    storage Storage
}

func New(storage Storage) *Controller {
    return &Controller{
        storage: storage,
    }
}

type Payload struct {
    Value string `json:"value"`
}

建立 storage.go:

package controllers

// Storage 接口支持存取单个值
type Storage interface {
    Get() string
    Put(string)
}

// MemStorage 实现了 Storage接口
type MemStorage struct {
    value string
}

func (m *MemStorage) Get() string {
    return m.value
}

func (m *MemStorage) Put(s string) {
    m.value = s
}

建立 post.go:

package controllers

import (
    "encoding/json"
    "net/http"
)

// SetValue 修改Controller的存储内容
func (c *Controller) SetValue(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }
    if err := r.ParseForm(); err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    value := r.FormValue("value")
    c.storage.Put(value)
    w.WriteHeader(http.StatusOK)
    p := Payload{Value: value}
    if payload, err := json.Marshal(p); err == nil {
        w.Write(payload)
    }

}

建立 get.go:

package controllers

import (
    "encoding/json"
    "net/http"
)

// GetValue是一个封装HandlerFunc的闭包,如果UseDefault为true,则值始终为“default”,否则它将是存储在storage中的任何内容
func (c *Controller) GetValue(UseDefault bool) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        if r.Method != "GET" {
            w.WriteHeader(http.StatusMethodNotAllowed)
            return
        }
        value := "default"
        if !UseDefault {
            value = c.storage.Get()
        }
        p := Payload{Value: value}
        w.WriteHeader(http.StatusOK)
        if payload, err := json.Marshal(p); err == nil {
            w.Write(payload)
        }
    }
}

建立 main.go:

package main

import (
    "fmt"
    "net/http"

    "github.com/agtorre/go-cookbook/chapter7/controllers"
)

func main() {
    storage := controllers.MemStorage{}
    c := controllers.New(&storage)
    http.HandleFunc("/get", c.GetValue(false))
    http.HandleFunc("/get/default", c.GetValue(true))
    http.HandleFunc("/set", c.SetValue)

    fmt.Println("Listening on port :3333")
    err := http.ListenAndServe(":3333", nil)
    panic(err)
}

运行:

go run main.go

这会输出

Listening on port :3333

进行请求测试:

$curl "http://localhost:3333/set -X POST -d "value=value"
{"value":"value"}
$curl "http://localhost:3333/get -X GET
{"value":"value"}
$curl "http://localhost:3333/get/default -X GET
{"value":"default"}

说明

这种策略有效,因为Go允许函数传递。我们可以用类似的方法传入数据库连接、日志记录等。在示例中,我们插入了一个storage接口,所有请求的处理方法都可以使用其方法和属性。

GetValue方法没有传递http.HandlerFunc签名,而是直接返回它,我们通过这种方式来注入状态。在main.go中,我们定义了两个路由,其中UseDefault设置为false,另一个路由设置为true。这可以在定义跨越多个路由的函数时使用,也可以在使用处理程序感觉过于繁琐的结构时使用。