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
 
