8、中间件(基础)
这个例子将展示如何在Go中创建基本的日志中间件。
中间件简单地接受一个
http.HandlerFunc
作为它的参数之一,包装它并返回一个新的
http.HandlerFunc
给服务器调用。
// basic-middleware.go
package main
import (
"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 Middleware
middleware := func(next http.HandlerFunc) http.HandlerFunc {
// Define the http.HandlerFunc which is called by the server eventually
handler := func(w http.ResponseWriter, r *http.Request) {
// ... do middleware things
// Call the next middleware/handler in chain
next(w, r)
}
// Return newly created handler
return handler
}
// Return newly created middleware
return middleware
}
这是完整的例子:
// advanced-middleware.go
package main
import (
"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 Middleware
return func(f http.HandlerFunc) http.HandlerFunc {
// Define the http.HandlerFunc
return func(w http.ResponseWriter, r *http.Request) {
// Do middleware things
start := time.Now()
defer func() { log.Println(r.URL.Path, time.Since(start)) }()
// Call the next middleware/handler in chain
f(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 Middleware
return func(f http.HandlerFunc) http.HandlerFunc {
// Define the http.HandlerFunc
return func(w http.ResponseWriter, r *http.Request) {
// Do middleware things
if r.Method != m {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Call the next middleware/handler in chain
f(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 main
import (
"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 authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Print secret message
fmt.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 authenticated
session.Values["authenticated"] = true
session.Save(r, w)
}
func logout(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "cookie-name")
// Revoke users authentication
session.Values["authenticated"] = false
session.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 main
import (
"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 User
json.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 main
import (
"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 simplicity
for {
// Read message from browser
msgType, msg, err := conn.ReadMessage()
if err != nil {
return
}
// Print the message to the console
fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))
// Write message back to browser
if 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 get
golang
bcrypt
库:
$ go get golang.org/x/crypto/bcrypt
从现在开始,我们编写的每个应用程序都可以使用这个库。
// passwords.go
package main
import (
"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 simplicity
fmt.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