> 文章列表 > Golang Web 开发 (二)

Golang Web 开发 (二)

Golang Web 开发 (二)

8、中间件(基础)

这个例子将展示如何在Go中创建基本的日志中间件。
中间件简单地接受一个http.HandlerFunc作为它的参数之一,包装它并返回一个新的http.HandlerFunc给服务器调用。

// basic-middleware.go
package mainimport ("fmt""log""net/http"
)func logging(f http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {log.Println(r.URL.Path)f(w, r)}
}func foo(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "foo")
}func bar(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "bar")
}func main() {http.HandleFunc("/foo", logging(foo))http.HandleFunc("/bar", logging(bar))http.ListenAndServe(":8080", nil)
}
$ go run basic-middleware.go
2017/02/10 23:59:34 /foo
2017/02/10 23:59:35 /bar
2017/02/10 23:59:36 /foo?bar$ curl -s http://localhost:8080/foo
$ curl -s http://localhost:8080/bar
$ curl -s http://localhost:8080/foo?bar

9、中间件(高级)

这个例子将展示如何在Go中创建一个更高级的中间件版本。

中间件本身只需要一个http.HandlerFunc作为它的参数之一,包装它并返回一个新的http.HandlerFunc给服务器调用。

在这里,我们定义了一种新的类型Middleware,它使得将多个中间件链接在一起更容易。这个想法的灵感来自于Mat Ryers关于构建api的演讲。你可以在这里找到包括演讲在内的更详细的解释。

这段代码详细解释了如何创建新的中间件。在下面的完整示例中,我们通过一些样板代码简化了这个版本。

func createNewMiddleware() Middleware {// Create a new Middlewaremiddleware := func(next http.HandlerFunc) http.HandlerFunc {// Define the http.HandlerFunc which is called by the server eventuallyhandler := func(w http.ResponseWriter, r *http.Request) {// ... do middleware things// Call the next middleware/handler in chainnext(w, r)}// Return newly created handlerreturn handler}// Return newly created middlewarereturn middleware
}

这是完整的例子:

// advanced-middleware.go
package mainimport ("fmt""log""net/http""time"
)type Middleware func(http.HandlerFunc) http.HandlerFunc// Logging logs all requests with its path and the time it took to process
func Logging() Middleware {// Create a new Middlewarereturn func(f http.HandlerFunc) http.HandlerFunc {// Define the http.HandlerFuncreturn func(w http.ResponseWriter, r *http.Request) {// Do middleware thingsstart := time.Now()defer func() { log.Println(r.URL.Path, time.Since(start)) }()// Call the next middleware/handler in chainf(w, r)}}
}// Method ensures that url can only be requested with a specific method, else returns a 400 Bad Request
func Method(m string) Middleware {// Create a new Middlewarereturn func(f http.HandlerFunc) http.HandlerFunc {// Define the http.HandlerFuncreturn func(w http.ResponseWriter, r *http.Request) {// Do middleware thingsif r.Method != m {http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)return}// Call the next middleware/handler in chainf(w, r)}}
}// Chain applies middlewares to a http.HandlerFunc
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {for _, m := range middlewares {f = m(f)}return f
}func Hello(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "hello world")
}func main() {http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))http.ListenAndServe(":8080", nil)
}

10、会话(Sessions)

这个例子将展示如何使用Go中流行的gorilla/sessions包在会话cookie中存储数据。

Cookies是存储在用户浏览器中的小块数据,并在每次请求时发送到我们的服务器。在它们中,我们可以存储例如,用户是否登录到我们的网站,并找出他实际上是谁(在我们的系统中)。

在本例中,我们将只允许经过身份验证的用户在/secret页面上查看我们的秘密消息。要访问它,用户首先必须访问/login以获得有效的会话cookie,从而登录。此外,他可以访问/logout撤销他访问我们的秘密消息的权限。

// sessions.go
package mainimport ("fmt""net/http""github.com/gorilla/sessions"
)var (// It is recommended to use an authentication key with 32 or 64 bytes.// The encryption key, if set, must be either 16, 24, or 32 bytes to select// AES-128, AES-192, or AES-256 modes.key = []byte("super-secret-key")store = sessions.NewCookieStore(key)
)func secret(w http.ResponseWriter, r *http.Request) {session, _ := store.Get(r, "cookie-name")// Check if user is authenticatedif auth, ok := session.Values["authenticated"].(bool); !ok || !auth {http.Error(w, "Forbidden", http.StatusForbidden)return}// Print secret messagefmt.Fprintln(w, "The cake is a lie!")
}func login(w http.ResponseWriter, r *http.Request) {// Get 在将给定名称的会话添加到注册中心后返回给定名称的会话。// 如果会话不存在,则返回一个新会话。访问会话上的IsNew,以检查它是现有会话还是新会话。// 它返回一个新的会话,如果会话存在但无法解码,则返回一个错误。session, _ := store.Get(r, "cookie-name")// Authentication goes here// ...// Set user as authenticatedsession.Values["authenticated"] = truesession.Save(r, w)
}func logout(w http.ResponseWriter, r *http.Request) {session, _ := store.Get(r, "cookie-name")// Revoke users authenticationsession.Values["authenticated"] = falsesession.Save(r, w)
}func main() {http.HandleFunc("/secret", secret)http.HandleFunc("/login", login)http.HandleFunc("/logout", logout)http.ListenAndServe(":8080", nil)
}
$ go run sessions.go$ curl -s http://localhost:8080/secret
Forbidden$ curl -s -I http://localhost:8080/login
Set-Cookie: cookie-name=MTQ4NzE5Mz...$ curl -s --cookie "cookie-name=MTQ4NzE5Mz..." http://localhost:8080/secret
The cake is a lie!

11、JSON

这个例子将展示如何使用encoding/json包对JSON数据进行编码和解码

// json.go
package mainimport ("encoding/json""fmt""net/http"
)type User struct {Firstname string `json:"firstname"`Lastname  string `json:"lastname"`Age       int    `json:"age"`
}func main() {http.HandleFunc("/decode", func(w http.ResponseWriter, r *http.Request) {var user Userjson.NewDecoder(r.Body).Decode(&user)fmt.Fprintf(w, "%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)})http.HandleFunc("/encode", func(w http.ResponseWriter, r *http.Request) {peter := User{Firstname: "John",Lastname:  "Doe",Age:       25,}json.NewEncoder(w).Encode(peter)})http.ListenAndServe(":8080", nil)
}
$ go run json.go$ curl -s -XPOST -d'{"firstname":"Elon","lastname":"Musk","age":48}' http://localhost:8080/decode
Elon Musk is 48 years old!$ curl -s http://localhost:8080/encode
{"firstname":"John","lastname":"Doe","age":25}

12、Websockets

这个例子将展示如何在Go中使用websocket。我们将构建一个简单的服务器,它将响应我们发送给它的所有内容。为此,我们必须go get流行的gorilla/websocket库,如下所示:

$ go get github.com/gorilla/websocket

从现在开始,我们编写的每个应用程序都可以使用这个库。

// websockets.go
package mainimport ("fmt""net/http""github.com/gorilla/websocket"
)var upgrader = websocket.Upgrader{ReadBufferSize:  1024,WriteBufferSize: 1024,
}func main() {http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {conn, _ := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicityfor {// Read message from browsermsgType, msg, err := conn.ReadMessage()if err != nil {return}// Print the message to the consolefmt.Printf("%s sent: %s\\n", conn.RemoteAddr(), string(msg))// Write message back to browserif err = conn.WriteMessage(msgType, msg); err != nil {return}}})http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {http.ServeFile(w, r, "websockets.html")})http.ListenAndServe(":8080", nil)
}
<!-- websockets.html -->
<input id="input" type="text" />
<button onclick="send()">Send</button>
<pre id="output"></pre>
<script>var input = document.getElementById("input");var output = document.getElementById("output");var socket = new WebSocket("ws://localhost:8080/echo");socket.onopen = function () {output.innerHTML += "Status: Connected\\n";};socket.onmessage = function (e) {output.innerHTML += "Server: " + e.data + "\\n";};function send() {socket.send(input.value);input.value = "";}
</script>

13、Password Hashing (bcrypt)

本例将展示如何使用bcrypt对密码进行哈希。为此,我们必须像这样go getgolang bcrypt库:

$ go get golang.org/x/crypto/bcrypt

从现在开始,我们编写的每个应用程序都可以使用这个库。

// passwords.go
package mainimport ("fmt""golang.org/x/crypto/bcrypt"
)func HashPassword(password string) (string, error) {bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)return string(bytes), err
}func CheckPasswordHash(password, hash string) bool {err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))return err == nil
}func main() {password := "secret"hash, _ := HashPassword(password) // ignore error for the sake of simplicityfmt.Println("Password:", password)fmt.Println("Hash:    ", hash)match := CheckPasswordHash(password, hash)fmt.Println("Match:   ", match)
}
$ go run passwords.go
Password:  secret
Hash:  $2a$14$7LxzgCK.u0regxQiUyQc6.701P8FWCFvbEacAD8eP6Xw49Jq2sKuy
Match:  true