go 开发web应用

本文简单介绍如何用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

CONTENTS