本文简单介绍如何用go开发web应用。需要说明,web应用和web service是有区别的,简单点说,输出html算是web应用,而只输出api等数据是web服务。
hello world
创建server.go
package main
import (
"net/http"
"fmt"
)
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(":8080", nil)
}
- http.HandleFunc: 通过注册匿名方法处理所有请求
- listenAndServe: 监听一个端口,启动服务
$ curl http://127.0.0.1:8080/testpath -i
HTTP/1.1 200 OK
Date: Thu, 04 Jan 2018 02:28:07 GMT
Content-Length: 35
Content-Type: text/plain; charset=utf-8
Hello, you've requested: /testpath
这样,使用go自带库,就能实现一个简单的web应用。但是,自带的http库有一点做地不好,就是对路由的处理,比如分段带参数的path等处理不好。gorilla/mux
很好地补充了这点。
mux 使用
安装:
go get -u github.com/gorilla/mux
示例:
package main
import (
"net/http"
"fmt"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/user/{name}/id/{id}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
id := vars["id"]
fmt.Fprintf(w, "user: %s, id: %s\n", name, id)
})
http.ListenAndServe(":8080", r)
}
提供一个prefix:
r := mux.NewRouter()
s := r.PathPrefix("/api").Subrouter()
s.HandleFunc("/user", userHandler)
s.HandleFunc("/product", productHandler)
http.ListenAndServe(":8080", r)
指定method
r.HandleFunc("/articles", handler).Methods("GET")
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
关于mux的其他详细,请移步githubgorilla/mux。
模板template
所有模板引擎的语法都差不多,go的模板也类似,下面看示例: 文件结构:
$ tree
├── assets
│ └── css
│ └── styles.css
├── server.go
└── tmpl
└── users.html
创建server.go
package main
import (
"net/http"
"html/template"
"path/filepath"
)
type User struct {
Name string
Age int
}
type Users struct {
Title string
Users []User
}
func main() {
filePrefix, _ := filepath.Abs("./src/web_application")
// 静态文件位置配置
fs := http.FileServer(http.Dir(filePrefix + "/assets/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// 1. 模板位置
tmpl := template.Must(template.ParseFiles(filePrefix + "/tmpl/users.html"))
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
// 2. 数据
data := Users{
Title: "user group",
Users: []User{
{Name: "Henry", Age: 27,},
{Name: "Thoreau", Age: 29,}}}
// 3. 渲染
tmpl.Execute(w, data)
})
// 监听8080
http.ListenAndServe(":8080", nil)
}
创建users.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/css/styles.css">
<link>
</head>
<body>
<h3>User list</h3>
<table>
<tr>
<th>name</th>
<th>age</th>
</tr>
{{range .Users}}
<tr>
<td>{{.Name}}</td>
<td>{{.Age}}</td>
</tr>
{{end}}
</table>
</body>
</html>
样式:
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 20%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
组装数据,读取模板,渲染,输出。这样,一个简单的web应用(web application)就完成了。
过滤器
添加一个类似java servlet 中filter,打印日志
// 在main中注册http.HandleFunc("/", Chain(Hello,Logging()))
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
type Middleware func(http.HandlerFunc) http.HandlerFunc
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for _, m := range middlewares {
f = m(f)
}
return f
}
// handler - logging
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 Hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
如果有多个handler,可以直接写成http.HandleFunc("/", Chain(HandleFunc,Handler1(),Handler2()))
session和cookie
go自动的http库支持cookie存取,但不支持session,需要自己实现。其中cookie使用如下:
func (w http.ResponseWriter, r *http.Request) {
expires := time.Now().AddDate(1, 0, 0)
sessionId := uuid.Must(uuid.NewV4())
cookie := http.Cookie{Name: "sessionId", Value: sessionId.String(), Expires: expires}
http.SetCookie(w, &cookie)
}
gorilla/sessions是一个提供cookie、系统会话和基础结构的实现。
var store = sessions.NewCookieStore([]byte("something-very-secret"))
func MyHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
session.Values["foo"] = "bar"
session.Values[42] = 43
// Save it before we write to the response/return from the handler.
session.Save(r, w)
}
其中store的实现有很多,比如mysql,redis。 mysql示例:
package main
import (
"fmt"
"github.com/srinathgs/mysqlstore"
"net/http"
)
var store *mysqlstore.MySQLStore
func sessTest(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "foobar")
session.Values["bar"] = "baz"
session.Values["baz"] = "foo"
err = session.Save(r, w)
fmt.Printf("%#v\n", session)
fmt.Println(err)
}
func main() {
store, err := mysqlstore.NewMySQLStore("UN:PASS@tcp(<IP>:<PORT>)/<DB>?parseTime=true&loc=Local", <tablename>, "/", 3600, []byte("<SecretKey>"))
if err != nil {
panic(err)
}
defer store.Close()
http.HandleFunc("/", sessTest)
http.ListenAndServe(":8080", nil)
}
总结: 从上述示例看出,使用go开发web也是挺容易的,其他比如访问db、部署等也比较容易,暂且不谈。
[1]. 《Go web application》 [2]. https://gowebexamples.com