通过示例学习go web编程
排球混子
来源: https://gowebexamples.com/
Hello World
介绍
Go 是一门内置了许多功能的编程语言,并且已经内置了一个Web服务器。标准库中的net/http
包包含了有关HTTP协议的所有功能.
代码
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
})
http.ListenAndServe(":80", nil)
}
项目准备
新建一个项目,名称为gowebexamples mysql环境 可使用docker
HTTP Server
介绍
接下来将在Go中创建一个基本的HTTP服务, 第一步先讨论一下HTTP服务应该具备的能力
- 处理动态请求:处理来自浏览网站、登录帐户或发布图片的用户的传入请求
- 提供静态资源:向浏览器提供JS、CSS和图像,以创建用户的动态体验
- 接受连接:HTTP服务必须监听特定的端口以能够接受来自互联网的连接
处理动态请求
nte/http包包含了接受请求和动态处理请求所需要的所有工具, 我们可以通过http.HandleFunc
注册一个新的处理函数。第一个参数表示匹配的路径,第二个参数是要执行的方法。
http.HandleFunc("/", func(w, http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Welcome to my website")
})
对于动态方面, http.Request
包含关于请求及参数的所有信息。 你可以通过r.URL.Query().Get("token")
来读取GET的请求参数 或者 通过r.FormValue("email")
来读取POST的参数(来自HTML表单的字段)
提供静态资源
为了提供像JS、CSS和图像这样的静态资源, 我们通过内置http.FileServcer
指定一个url路径
。为了让服务器正常工作它需要知道从哪里提供文件。
fs := http.FileServer(http.Dir("static/"))
一旦我们的文件就位, 我们只需要指向这个url路径,像我们在处理动态请求时那样。 需要注意的一点是:为了正确的提供文件, 我们需要去掉url路径的一部分。通常是我们文件所在目录的名称。
http.Handle("/static/", http.StripPrefix("/static/", fs))
接受请求
为完成我们的静态服务器最后需要做的事, 去监听一个端口以接受来自互联网的请求。Go也有一个内置的HTTP server, 我们可以快速的启动。一旦启动,你可以通过浏览器查看你的HTTP server
http.ListenAndServer(":80", nil)
代码
// gowebexample/main.go
package main
import (
"fmt"
"net/http"
)
func main(){
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Welcome to my website!")
})
fs := http.FileServer(http.Dir("static/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.ListenAndServe(":80", nil)
}
Routing(使用 gorilla/mux)
介绍
Go的net/http
为HTTP协议提供了很多功能,但是它在如何将复杂的路由请求分割成单个参数上做的不是很好。幸运的是, Go社区中有个非常受欢迎的包,以良好的代码质量而闻名。在这个示例中,你会看到如何使用gorilla/mux
包来创建携带参数的路由、GET/POST处理和域名限制。
安装gorilla/mux
gorilla/mux
是一个适配Go默认HTTP路由器的包,它提供了很多功能以提高编写web的生产力。它还与Go默认的请求签名处理器func (w http.ResponseWriter, r *http.Request)
兼容,因此他可以跟其他HTTP库(比如中间件或现有应有)混合使用。
go get -u github.com/gorilla/mux
创建新的路由器
首先创建一个新的请求路由。 路由器是Web应用程序的主要路由器,并将参数传递给服务器。 它会接收所有的HTTP连接并将其传递给你在其上注册的请求处理器。
r := mux.NewRouter()
URL参数
gorilla/mux
路由器最大的优势就是可以从请求URL上提取片。例如,这是你应用程序的URL。
/books/go-programming/page/10
URL有两个动态片段 书名 (go-programming) 页数 (10) 要让请求处理器匹配上述的URL匹配,你可以在你的URL模式中替换动态片段为占位符,像这样:
r.HandleFunc("/books/{title}/page/{page}", func(w http.ResponseWriter, r *http.Request) {
// get the book
// navigate to the page
})
最后一件事是从URL片段中获取数据。 这个包提供了mux.Vars(r)
函数,它以http.Request
作为参数并返回一个片段映射
func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
vars["title"] // the book
vars["page"] // the page
}
设置HTTP服务的路由器
有想过nil
在http.ListenAndServe(":80", nil)
是什么意思吗?这个参数是HTTP服务主路由器的参数。默认情况下是nil
。代表着默认使用net/http
包。要是用你自己的路由器,替换nil
变量为你的路由器r
http.ListenAndServer(":80", r)
代码
// gowebexample/main.go
package main
import (
"fmt"
"github.com/gorilla/mux" "net/http")
func main() {
r := mux.NewRouter()
r.HandleFunc("/books/{title}/page/{page}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
title := vars["title"]
page := vars["page"]
fmt.Fprintf(w, "You've requested the book: %s on page %s\n", title, page)
})
http.ListenAndServe(":80", r)
}
gorilla/mux路由器的功能
Methods 限制请求处理器为特定的HTTP方法
```go
r.HandleFunc("/books/{title}", CreateBook).Methods("POST")
r.HandleFunc("/books/{title}", ReadBook).Methods("GET")
r.HandleFunc("/books/{title}", UpdateBook).Methods("PUT")
r.HandleFunc("/books/{title}", DeleteBook).Methods("DELETE")
域名 & 子域名
限制请求处理器为特定的域名或子域名
r.HandleFunc("/books/{title}", BookHandler).Host("www.mybookstore.com")
Schemes
限制请求处理器为 http/https
r.HandleFunc("/secure", SecureHandler).Schemes("Https")
r.HandleFunc("/insecure", InsecureHandler).Schemes("Http")
MySQL Database
介绍
懒得介绍,数据库的作用就是为了存储数据。在Web开发中,一个常用的数据库是MySQL数据库。在这个示例,将介绍在Go中与数据库交互的一些操作。
安装
Go语言提供一个了便捷的包,称作database/sql
用来查询各种SQL数据库。这对你来说很有用,因为它将所有常见的SQL特性都抽象成一个单一的API供你使用。不过Go没有包含数据库驱动。在Go中,数据库驱动是一个实现特定数据库的底层细节的包。
go get -u github.com/go-sql-driver/mysql
连接MySQL数据库
在安装完所有必须的包之后我们需要检查的第一件事是: 如果我们不能成功连接我们的MySQL数据库,如果你没有已经运行的MySQL数据库服务,你可以用Docker启动一个。
为了检查我们能否连接数据库,引入database/sql
和go-sql-driver/mysql
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@(127.0.0.1:3307)/your_db_name?parseTime=true")
if err != nil {
fmt.Println("Failed to connect to the database", err)
return
}
err = db.Ping()
if err != nil {
fmt.Println("Failed to ping the database", err)
return
}
fmt.Println("Connected to the database")
}
代码
package main
import (
"database/sql"
"fmt" _ "github.com/go-sql-driver/mysql"
"log" "time")
type User struct {
id int
username string
password string
createdAt time.Time
}
func main() {
db, err := sql.Open("mysql", "user:password@(127.0.0.1:3307)/your_db_name?parseTime=true")
if err != nil {
fmt.Println("Failed to connect to the database", err)
return
}
err = db.Ping()
if err != nil {
fmt.Println("Failed to ping the database", err)
return
}
fmt.Println("Connected to the database")
{ // 创建新表
query := `
CREATE TABLE users ( id INT AUTO_INCREMENT, username TEXT NOT NULL, password TEXT NOT NULL, created_at DATETIME, PRIMARY KEY (id) );`
_, err = db.Exec(query)
if err != nil {
fmt.Println("Failed to Exec the query")
}
fmt.Println("Exec the query")
}
{ // 查询单个用户
username := "Vic"
password := "secret"
createdAt := time.Now()
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)
if err != nil {
fmt.Println("Failed to Exec the query of the insert")
}
userId, err := result.LastInsertId()
if err != nil {
fmt.Println("Failed to get the LastInsertId")
}
fmt.Printf("userId is %s\n", userId)
}
{ // 查询全部用户
var user User
query := `SELECT id, username, password, created_at FROM users WHERE id = ?`
err = db.QueryRow(query, 1).Scan(&user.id, &user.username, &user.password, &user.createdAt)
if err != nil {
fmt.Println("Failed to Exec the query of the Query", err)
return
}
fmt.Println(user, user.createdAt)
}
{ // Query all users
rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)
if err != nil {
fmt.Println("Failed to Query")
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.id, &user.username, &user.password, &user.createdAt)
if err != nil {
log.Fatal(err)
}
users = append(users, user)
}
err = rows.Err()
if err != nil {
fmt.Println("err is", err)
}
fmt.Printf("%#v", users)
}
}
中间件(基础)
介绍
中间件只需要用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)
}
中间件(高级)
介绍
中间件本身只是将 http.HandlerFunc
作为其参数之一,对其进行包装并返回一个新的 http.HandlerFunc
,以便服务器调用。
在这里,我们定义了一个新类型 Middleware
,它使链多个中间件在一起变得更容易
package main
import (
"fmt"
"log" "net/http" "time")
//func createNewMiddleware() Middleware {
// Middleware := func(next http.HandlerFunc) http.HandlerFunc {
// handler := func(w http.ResponseWriter, r *http.Request) {
//
// next(w, r)
// }
// return handler
// }
// return Middleware
//}
type Middleware func(handlerFunc http.HandlerFunc) http.HandlerFunc
func Logging() Middleware {
return func(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() { log.Println(r.URL.Path, time.Since(start)) }()
f(w, r)
}
}
}
func Method(m string) Middleware {
return func(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != m {
http.Error(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
}
f(w, r)
}
}
}
func Chain(f http.HandlerFunc, middleware ...Middleware) http.HandlerFunc {
for _, m := range middleware {
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("POST"), Logging()))
http.ListenAndServe(":8080", nil)
}
Sessions
介绍
这个示例将展示如何在Go中使用流行的gorilla/sessions
包将数据存储在会话cookie中。
Cookie是存储在用户浏览器中的小数据片段,并在每个请求中发送到我们的服务器。在这些cookie中,我们可以存储用户是否已登录到我们的网站,以及在我们的系统中他到底是谁。
// sessions.go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/sessions"
)
var (
// key must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256)
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) {
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!
JSON
// json.go
package main
import (
"encoding/json"
"fmt" "net/http")
type JsonUser 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 JsonUser
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 := JsonUser{
Firstname: "John",
Lastname: "Doe",
Age: 32,
}
json.NewEncoder(w).Encode(peter)
})
http.ListenAndServe(":8080", nil)
}
Websockets
这个示例将展示Websocket在Go中如何工作,我们会构建一个简单的服务, 它会回显我们发送的一切内容。我们需要安装gorilla/websocket
库。
go get github.com/gorilla/websocket
代码
Go
// websockets.go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/websocket")
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func main() {
http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
if err != nil {
fmt.Println("Error upgrading connection:", err)
return
}
defer conn.Close() // 确保在函数退出时关闭连接
for {
// Read message from browser
msgType, msg, err := conn.ReadMessage()
if err != nil {
return
}
// Print the message to the console
fmt.Printf("%v 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")
})
err := http.ListenAndServe(":8080", nil)
if err != nil {
return
}
}
html
<!-- 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>
密码哈希
此示例展示如何使用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 := "KFC-VME50$"
hash, err := HashPassword(password)
if err != nil {
fmt.Println(err)
}
fmt.Println("Password:", password)
fmt.Println("Hash:", hash)
match := CheckPasswordHash(password, hash)
fmt.Println("Match: ", match)
}