1.go写一个简单的文件服务器
package main
import "net/http"
func main(){
http.ListenAndServe(":9090",http.FileServer(http.Dir("var/www")))
}
2.简单的服务和默认的路由
package main
import (
"net/http"
"fmt"
"time"
"html"
"log"
)
func main(){
http.HandleFunc("/",showInfo)
http.HandleFunc("/site",serveFile)
err := http.ListenAndServe(":9090",nil)
if err != nil{
log.Fatal(err)
}
}
func showInfo(w http.ResponseWriter,r *http.Request){
fmt.Fprintln(w,"Current time: ",time.Now())
fmt.Fprintln(w,"URL Path: ",html.EscapeString(r.URL.Path))
}
func serveFile(w http.ResponseWriter,r *http.Request){
http.ServeFile(w,r,"index.html")
}
在这里,main函数是这个程序的入口,通过 http.HandlerFunc(route,handler) 方法设置了服务路由,每个URL路由对应了一个方法,这个方法就是响应客户端的请求. http.ListenAndServe() 方法,会开启一个HTTP服务器,参数包含了地址和路由,当路由为nil时,使用默认的路由DefaultServeMux.
响应方法执行不同的操作,依据URL的访问,"/"或者"/site",在这里的两个方法的参数都是一样的,http.ResponseWriter和http.Request,如果一个响应方法的参数不符合这样的规范而且传入到http.HandleFunc(),就会编译错误.
当我们访问 /site 时,http.ServeFile()方法响应请求,并且返回 index.html页面.当我们访问根目录 / 时,showInfo()方法会被调用,展示当前的时间和访问的路径.
接下来我们拓展一下上面的代码,使用StripPrefix
package main
import (
"net/http"
"fmt"
"time"
"html"
"log"
)
func main(){
http.HandleFunc("/",showInfo)
//http.HandleFunc("/site",serveFile)
files := http.FileServer(http.Dir("var/www"))
http.Handle("/site/",http.StripPrefix("/site/",files))
err := http.ListenAndServe(":9090",nil)
if err != nil{
log.Fatal(err)
}
}
func showInfo(w http.ResponseWriter,r *http.Request){
fmt.Fprintln(w,"Current time: ",time.Now())
fmt.Fprintln(w,"----URL Path: ",html.EscapeString(r.URL.Path))
}
在这里,我们调用了Handle,不再是HandleFunc.因为 FileServer方法返回的是 handler,所以我们讲Handle传递到路由.
2.中间件
在web开发中,中间代码一般在请求和路由响应(handler)之间.中间件代码一般都是可以复用完成一些任务,在请求响应之前或者之后.在其他语言中类似的 interceptor(拦截器),hooking,filter(过滤器).
比如,我们也许想在路由请求前对检查数据库连接的状态或者对一个用户授权.又比如我们想压缩响应的内容或者限制请求的时间.
在Go web开发中,创建中间件就是很多链式的handler和handler函数.最根本的想法:传递一个handler函数f2作为参数到另外一个handler函数f1. f1在接收到请求时就会被调用,如果f1进行了某些操作,接下来就调用f2.
基本的例子:
package main
import (
"net/http"
"fmt"
)
func middleware1(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter,r *http.Request) {
fmt.Fprintln(w,"Executing middleware1()")
next.ServeHTTP(w,r)
fmt.Fprintln(w,"Executing middleware1() again")
})
}
func middleware2(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter,r *http.Request) {
fmt.Fprintln(w,"Executing middleware2()")
if r.URL.Path != "/"{
return
}
next.ServeHTTP(w,r)
fmt.Fprintln(w,"Executing middleware() again")
})
}
func final(w http.ResponseWriter,r *http.Request){
fmt.Fprintln(w,"Executing final()...")
fmt.Fprintln(w,"Done")
}
func main(){
finalHandler := http.HandlerFunc(final)
http.Handle("/",middleware1(middleware2(finalHandler)))
http.ListenAndServe(":9090",nil)
}
运行结果
Executing middleware1()
Executing middleware2()
Executing final()...
Done
Executing middleware() again
Executing middleware1() again
在这里,使用了middleware1 handler拦截了根目录的请求,这个handler接收另外一个handler作为参数middleware2,接着final handler作为参数传递到middleware2.
当访问 "/"时,就会调用middleware1,先展示一段文字消息,接着调用next.serveHTPP,执行middleware2的代码.middleware2函数接着调用final函数,执行完后回到middleware2,middleware2执行完成回到middleware1接着执行.
Go内部经常使用中间件.比如,net/http包下的很多函数,StripPrefix,包裹了handler同时对response和request进行另外的操作.
中间件的概念有时候挺难理解的,其中的另外一种写法:
package main
import "net/http"
type AfterMiddleware struct {
handler http.Handler
}
func (a *AfterMiddleware)ServeHTTP(w http.ResponseWriter,r *http.Request){
a.handler.ServeHTTP(w,r)
w.Write([]byte(" +++ Hello from middleware! +++"))
}
func myHandler(w http.ResponseWriter,r *http.Request){
w.Write([]byte("*** Hello from myHandler! ***"))
}
func main(){
mid := &AfterMiddleware{http.HandlerFunc(myHandler)}
println("Listening on port 9090")
http.ListenAndServe(":9090",mid)
}
这里的代码,在myHandler函数写入数据到response,通过中间件的执行,添加数据到response.
http.Handler接口仅需要实现ServeHTPP方法
type Handler interface{
ServeHTTP(ResponseWriter,*Request)
}
运行结果:
*** Hello from myHandler! *** +++ Hello from middleware! +++
3.使用Gorilla Web Toolkit组件
3.1使用gorilla/mux
package main
import (
"net/http"
"github.com/gorilla/mux"
"log"
"os"
)
func pageHandler(w http.ResponseWriter,r *http.Request){
vars := mux.Vars(r)
productId := vars["id"]
log.Printf("Product Id:%v\n",productId)
filename := productId+".html"
if _,err := os.Stat(filename);os.IsNotExist(err){
log.Printf("no such product")
filename = "invalid.html"
}
http.ServeFile(w,r,filename)
}
func main(){
router := mux.NewRouter()
router.HandleFunc("/product/{id:[0-9]+}",pageHandler)
http.Handle("/",router)
http.ListenAndServe(":9090",nil)
}
上面的功能是gorilla/mux最基础的.比如,还可以
路径前缀: router.PathPrefix("/products/")
HTTP方法:router.Methods("Get","POST")
URL协议:router.Schemes("https")
header值设置:router.Headers("X-Request-Width","XMLHttpRequest")
router.Queries("key","value")
自定义:
router.MatcherFunc(func(r *http.Request, match *RouteMatch) bool {
// do something
})
可以把多个方法链式调用:
router.HandleFunc("/products", productHandler). Host("www.example.com").
Methods("GET").
Schemes("https")
还可以使用子路由:
router := mux.NewRouter()
subrouter := route.Host("www.example.com").Subrouter()
// Register the subroutes
subrouter.HandleFunc("/products/", AllProductsHandler)
subrouter.HandleFunc("/products/{name}", ProductHandler)
subrouter.HandleFunc("/reviews/{category}/{id:[0-9]+}"), ReviewsHandler)
4.返回错误
// Return 404 Not Found
http.NotFound(w, req)
// Return 301 Permanently Moved
http.Redirect(w, req, “http://somewhereelse.com”, 301)
// Return 302 Temporarily Moved
http.Redirect(w, req “http://temporarylocation”, 302)
5.访问数据库 MySQL
创建数据库world和表city
CREATE DATABASE world;
CREATE TABLE `city` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(20) DEFAULT NULL,
`CountryCode` varchar(20) DEFAULT NULL,
`District` varchar(20) DEFAULT NULL,
`Population` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
Go进行数据库访问操作
package main
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
"net/http"
"fmt"
)
type City struct {
Name string
CountryCod string
Population int
}
var database *sql.DB
func main(){
db,err := sql.Open("mysql","root:root@tcp(127.0.0.1:3306)/world")
if err != nil{
log.Println("Could not connect!")
}
database = db
log.Println("Connected")
http.HandleFunc("/",showCity)
http.ListenAndServe(":9090",nil)
}
func showCity(w http.ResponseWriter,r *http.Request){
city := City{}
queryParam := "%"+r.URL.Path[1:]+"%"
rows,err := database.Query("SELECT Name,CountryCode,Population FROM city WHERE CountryCode LIKE ?",queryParam)
if err != nil{
log.Fatal(err)
}
defer rows.Close()
for rows.Next(){
err := rows.Scan(&city.Name,&city.CountryCod,&city.Population)
if err!=nil{
log.Fatal(err)
}
fmt.Fprintf(w,"%s (%s),Population:%d \n",city.Name,city.CountryCod,city.Population)
}
err = rows.Err()
if err != nil{
log.Fatal(err)
}
}
sql.DB:
注意到,上面的代码使用了sql.DB变量实例database来进行访问数据库,sql.DB并不是数据库的链接,而是存贮在dbConn,是database/sql包的数据库抽象.上面的例子,对应的是MySQL数据库,但是我们可以使用database/sql去接入多个不同的数据库源.就算简单的本地文件或者存储在memcache上的数据.
sql.DB背后做了很多事情,包括打开,关闭和连接池.不要一直打开和关闭数据库,除非有必要.sql.DB被设计成长存在,所有和数据库交互都用到.当然我们可以使用sql.DB.Close关闭连接,放到连接池.
使用HTML展示信息
func showCity(w http.ResponseWriter,r *http.Request){
city := City{}
queryParam := "%"+r.URL.Path[1:]+"%"
rows,err := database.Query("SELECT Name,CountryCode,Population FROM city WHERE CountryCode LIKE ?",queryParam)
if err != nil{
log.Fatal(err)
}
defer rows.Close()
html := "<html><head><title>City Search</title></head><body><h1>Search for"+ queryParam + "</h1><table border='1'><tr><th>City</th><th>Country Code</th><th>Population</th></tr>"
for rows.Next(){
err := rows.Scan(&city.Name,&city.CountryCod,&city.Population)
if err!=nil{
log.Fatal(err)
}
html += fmt.Sprintf("<tr><td>%s</td><td>%s</td><td>%d</td></tr>",city.Name, city.CountryCod, city.Population)
//fmt.Fprintf(w,"%s (%s),Population:%d \n",city.Name,city.CountryCod,city.Population
}
err = rows.Err()
if err != nil{
log.Fatal(err)
}else {
html += "</table></body></html>"
fmt.Fprintln(w,html)
}
}
结果:

6.templates
Go的template由template包提供,包含了很多方法,包括Parse和ParseFile,对应了加载字符串模板和文件模板,然后执行Execute方法进行整合参数和模板.
常见的模板一般是文件,我们也可以将模板编码成一个字符串,使用Parse方法进行读取.
6.1第一个模板程序
//hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello!</title>
</head>
<body>
<h1>A warm hello to ...</h1>
<p>
{{.Name}}
</p>
</body>
</html>
main.go
package main
import (
"net/http"
"html/template"
"log"
)
type Person struct {
Name string
}
func main(){
http.HandleFunc("/",handler)
err := http.ListenAndServe(":9090",nil)
if err != nil{
log.Fatal(err)
}
}
func handler(w http.ResponseWriter,r *http.Request){
p := Person{"Tom"}
t,_ := template.ParseFiles("hello.html")
t.Execute(w,p)
}
运行结果:
A warm hello to ...
Tom
6.2把城市信息展示的程序使用模板展示
//city.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>City Search</title>
</head>
<body>
<h1>Search Results</h1>
<table border="1">
<tr>
<th>City</th>
<th>Country Code</th>
<th>Population</th>
</tr>
{{range .}}
<tr>
<td>{{.Name}}</td>
<td>{{.CountryCode}}</td>
<td>{{.Population}}</td>
</tr>
{{end}}
</table>
</body>
</html>
main.go
package main
import (
"database/sql"
"log"
"net/http"
_ "github.com/go-sql-driver/mysql"
"html/template"
"fmt"
)
type City struct {
Name string
CountryCode string
Population int
}
var database *sql.DB
func main(){
db,err := sql.Open("mysql","root:root@tcp(127.0.0.1:3306)/world")
if err != nil{
log.Println("Could not connected!")
}
database = db
log.Println("Connected.")
http.HandleFunc("/",showCity)
http.ListenAndServe(":9090",nil)
}
func showCity(w http.ResponseWriter,r *http.Request){
var Cities = []City{}
queryParam := "%" + r.URL.Path[1:]+ "%"
cities,err := database.Query("SELECT Name ,CountryCode,Population FROM city WHERE CountryCode like ?",queryParam)
if err != nil{
log.Fatal(err)
}
defer cities.Close()
for cities.Next(){
theCity := City{}
cities.Scan(&theCity.Name,&theCity.CountryCode,&theCity.Population)
Cities = append(Cities,theCity)
}
fmt.Println(Cities)
t,_ := template.ParseFiles("city.html")
t.Execute(w,Cities)
}
6.3在模板中使用方法
//Adding a Custom Function to the City Struct
func (c City)FormatPopulation(n int,sep rune)string{
s := strconv.Itoa(n)
startOffset := 0
var buff bytes.Buffer
if n < 0{
startOffset = 1
buff.WriteByte('-')
}
l := len(s)
commaIndex := 3 -((l-startOffset)%3)
if 3 == commaIndex{
commaIndex = 0
}
for i := startOffset;i<l;i++{
if 3 == commaIndex{
buff.WriteRune(sep)
commaIndex = 0
}
commaIndex++
buff.WriteByte(s[i])
}
return buff.String()
}
city.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>City Search</title>
</head>
<body>
<h1>Search Results</h1>
<table border="1">
<tr>
<th>City</th>
<th>Country Code</th>
<th>Population</th>
</tr>
{{range .}}
<tr>
<td>{{.Name}}</td>
<td>{{.CountryCode}}</td>
<td>{{.FormatPopulation .Population ','}}</td>
</tr>
{{end}}
</table>
</body>
</html>
6.4模板中使用条件语句
我们可以在模板中使用条件语句:{{if}},{{else}},{{end}}
Go对基础类型支持很多方法比如 <b>eq</b>(equal),<b>ne</b>(not equal to)或者<b>gt</b>(greater than)等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>City Search</title>
</head>
<body>
<h1>Search Results</h1>
<table border="1">
<tr>
<th>City</th>
<th>Country Code</th>
<th>Population</th>
</tr>
{{range .}}
<tr>
<td>{{.Name}}</td>
<td>{{.CountryCode}}</td>
<!--<td>{{.FormatPopulation .Population ','}}</td>-->
<td>{{if gt .Population 2205}}<b>HUGE</b>
{{else}}{{.Population}}
{{end}}
</td>
</tr>
{{end}}
</table>
</body>
</html>
结果:

7.Resultful json api
这个小程序涉及的文件
main.go //程序入口
handlers.go //响应方法
router.go //路由器
routes.go //路由设计
database.go //数据库操作
city.go //model
dbupdate.go //数据操作返回模型
7.1服务和路由
routes.go,定义了每个路由和响应的方法.
//toutes.go
package main
import "net/http"
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
type Routes []Route
var routes = Routes{
Route{
"HomePage",
"GET",
"/",
HomePage,
},
Route{
"CityList",
"GET",
"/city",
CityList,
},
Route{
"CityDisplay",
"GET",
"/city/{id}",
CityDisplay,
},
Route{
"CityAdd",
"POST",
"/cityadd",
CityAdd,
},
Route{
"CityDelete",
"GET",
"/citydel/{id}",
CityDelete,
},
Route{
"CityUpdate",
"POST",
"/cityupd",
CityUpdate,
},
}
创建路由器
//router.go
package main
import "github.com/gorilla/mux"
func NewRouter() *mux.Router{
router := mux.NewRouter().StrictSlash(true)
for _,route := range routes{
router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(route.HandlerFunc)
}
return router
}
数据库操作设计
//database.go
package main
import (
"database/sql"
"log"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var database *sql.DB
// Connect to the "world" database
func dbConnect() {
// replace "root" and "password" with your database login credentials
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/world")
if err != nil {
log.Println("Could not connect!")
}
database = db
log.Println("Connected.")
}
handler设计
//handlers.go
package main
import (
"net/http"
"github.com/gorilla/mux"
"strconv"
"io/ioutil"
"io"
"encoding/json"
"fmt"
)
func HomePage(w http.ResponseWriter,r *http.Request){
fmt.Fprintln(w,"Welcome to the city Database")
}
func CityList(w http.ResponseWriter,r *http.Request){
jsonCities := dbCityList()
w.Header().Set("Content-Type","application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonCities)
}
func CityDisplay(w http.ResponseWriter,r *http.Request){
vars := mux.Vars(r)
cityId,_ := strconv.Atoi(vars["id"])
jsonCity := dbCityDisplay(cityId)
w.Header().Set("Content-Type","application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonCity)
}
func CityAdd(w http.ResponseWriter,r *http.Request){
var city City
body,err := ioutil.ReadAll(io.LimitReader(r.Body,1048576))
if err != nil{
panic(err)
}
if err := r.Body.Close();err != nil{
panic(err)
}
if err := json.Unmarshal(body,&city);err != nil{
w.Header().Set("Content-Type","application/json")
w.WriteHeader(422)
if err := json.NewEncoder(w).Encode(err);err != nil{
panic(err)
}
}
fmt.Println(city)
addResult := dbCityAdd(city)
w.Header().Set("Content-Type","application/json")
w.WriteHeader(http.StatusCreated)
w.Write(addResult)
}
func CityDelete(w http.ResponseWriter,r *http.Request){
vars := mux.Vars(r)
cityId,_ := strconv.ParseInt(vars["id"],10,64)
deleteResult := dbCityDelete(cityId)
w.Header().Set("Content-Type","application/json")
w.WriteHeader(http.StatusOK)
w.Write(deleteResult)
}
func CityUpdate(w http.ResponseWriter,r *http.Request){
var city City
body,err := ioutil.ReadAll(io.LimitReader(r.Body,1048576))
if err != nil{
panic(err)
}
if err := r.Body.Close();err != nil{
panic(err)
}
if err := json.Unmarshal(body,&city);err != nil{
w.Header().Set("Content-Type","application/json")
w.WriteHeader(422)
if err := json.NewEncoder(w).Encode(err);err != nil{
panic(err)
}
}
fmt.Println(city)
updateCity := dbUpdateCity(city)
w.Header().Set("Content-Type","application/json")
w.WriteHeader(http.StatusOK)
w.Write(updateCity)
}
对应的,我们需要在database.go中添加对应的数据库操作方法
//database.go
func dbCityList()[]byte{
var cities Cities
var city City
cityResults,err := database.Query("SELECT * FROM city")
if err != nil{
log.Fatal(err)
}
defer cityResults.Close()
for cityResults.Next(){
cityResults.Scan(&city.Id,&city.Name,&city.CountryCode,&city.District,&city.Population)
cities = append(cities,city)
}
jsonCities,err := json.Marshal(cities)
if err != nil{
fmt.Printf("Error: %s",err)
}
return jsonCities
}
func dbCityDisplay(cityId int)[]byte{
var city City
err := database.QueryRow("SELECT * FROM city WHERE Id=?",cityId).Scan(&city.Id,&city.Name,&city.CountryCode,&city.District,&city.Population)
if err != nil{
log.Fatal(err)
}
jsonCity,err := json.Marshal(city)
if err != nil{
log.Fatal(err)
}
return jsonCity
}
func dbCityAdd(city City)[]byte{
var addResult DBUpdate
stmt,err := database.Prepare("INSERT INTO city(Name,CountryCode,District,Population) VALUES(?,?,?,?)")
if err != nil{
log.Fatal(err)
}
res,err := stmt.Exec(city.Name,city.CountryCode,city.District,city.Population)
if err != nil{
log.Fatal(err)
}
lastId,err := res.LastInsertId()
if err != nil{
log.Fatal(err)
}
rowCnt,err := res.RowsAffected()
if err != nil{
log.Fatal(err)
}
addResult.Id = lastId
addResult.Affected = rowCnt
newCity,err := json.Marshal(addResult)
if err != nil{
fmt.Printf("Error: %s",err)
}
return newCity
}
func dbCityDelete(id int64) []byte {
var deleteResult DBUpdate
stmt,err := database.Prepare("DELETE FROM city WHERE Id=?")
if err != nil{
log.Fatal(err)
}
res,err := stmt.Exec(id)
if err != nil{
log.Fatal(err)
}
rowCnt,err := res.RowsAffected()
if err != nil{
log.Fatal(err)
}
deleteResult.Id = id
deleteResult.Affected = rowCnt
deleteCity,err := json.Marshal(deleteResult)
if err != nil{
fmt.Printf("Error:%s",err)
}
return deleteCity
}
func dbUpdateCity(city City)[]byte{
var theCity City
err := database.QueryRow("SELECT * FROM city WHERE Id=?",city.Id).Scan(&theCity.Id,&theCity.Name,&theCity.CountryCode,&theCity.District,&theCity.Population)
if err != nil{
log.Fatal(err)
return nil
}
if city.Name != ""{
theCity.Name = city.Name
}
if city.CountryCode != ""{
theCity.CountryCode = city.CountryCode
}
if city.District != ""{
theCity.District = city.District
}
if city.Population != 0{
theCity.Population = city.Population
}
var updateResult DBUpdate
stmt,err := database.Prepare("UPDATE city set Name=?,CountryCode=?,District=?,Population=? WHERE Id=?")
if err != nil{
log.Fatal(err)
}
res,err := stmt.Exec(theCity.Name,theCity.CountryCode,theCity.District,theCity.Population,theCity.Id)
if err != nil{
log.Fatal(err)
}
rowCnt,err := res.RowsAffected()
if err != nil{
log.Fatal(err)
}
updateResult.Id = int64(city.Id)
updateResult.Affected = rowCnt
updateCity,err := json.Marshal(updateResult)
if err != nil{
fmt.Printf("Error:%s",err)
}
return updateCity
}
模型设计
//dbupdate.go
package main
type DBUpdate struct {
Id int64 `json:"id"`
Affected int64 `json:"affected"`
}
//city.go
package main
type City struct {
Id int
Name string
CountryCode string
District string
Population int
}
type Cities []City
最后,主程序入口
//main.go
package main
import (
"log"
"net/http"
)
func main(){
router := NewRouter()
dbConnect()
log.Fatal(http.ListenAndServe(":9090",router))
}
运行程序测试,在终端进入到项目文件,go run *.go
测试结果
//查询全部结果
curl -i localhost:9090/city
[{"Id":1,"Name":"北京","CountryCode":"001","District":"朝阳","Population":2202},
{"Id":2,"Name":"Haikou","CountryCode":"007","District":"Longhua","Population":2209},
{"Id":3,"Name":"深圳","CountryCode":"003","District":"南山","Population":2205},
{"Id":4,"Name":"广州","CountryCode":"004","District":"海珠","Population":2208},
{"Id":5,"Name":"成都","CountryCode":"005","District":"蓉城","Population":2206},
{"Id":10,"Name":"Haikou","CountryCode":"007","District":"Longhua","Population":2209}]
//查询某个城市信息
//curl -i localhost:9090/city/3
{"Id":3,"Name":"深圳","CountryCode":"003","District":"南山","Population":2205}
//curl -H "Content-Type: application/json" -d '{"Name":"Haikou","CountryCode":"007","District":"Longhua","Population":2209}' http://localhost:9090/cityadd
{"id":11,"affected":1}
//删除某个城市信息
//curl -i localhost:9090/citydel/10
{"id":10,"affected":1}
8.cookies 和session
8.1 cookie
设置cookie的方法
http.SetCookie(w ResponseWriter,cookie *cookie)
cookie 是一个结构体
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string
}
Name:cookie的键
Value:cookie值
Expires:cookie时间,过期会被删除
eg:
expiration := time.Now().Add(365 * 24 * time.Hour)
cookie := http.Cookie{Name: "username", Value: "jsmith", Expires: expiration} http.SetCookie(w, &cookie)
使用cookie
package main
import (
"net/http"
"time"
"strconv"
)
func CheckLastVisit(w http.ResponseWriter,r *http.Request){
c,err := r.Cookie("lastvisit")
expiry := time.Now().AddDate(0,0,1)
cookie := &http.Cookie{Name:"lastvisit",Value:strconv.FormatInt(time.Now().Unix(),10),Expires:expiry,}
http.SetCookie(w,cookie)
if err != nil{
w.Write([]byte("Welcome to the site!"))
}else {
lasttime,_ := strconv.ParseInt(c.Value,10,0)
html := "Welcome back! You last visited at: "
html = html + time.Unix(lasttime,0).Format("15:04:05")
w.Write([]byte(html))
}
}
func main(){
http.HandleFunc("/",CheckLastVisit)
http.ListenAndServe(":9090",nil)
}
在这里,我们先是获取cookie的值,如果存在,我们就欢迎用户再次回来,并且先是上次访问的时间.如果不存在,就直接欢迎用户.
8.2 使用session,github.com/gorilla/sessions
package main
import (
"net/http"
"github.com/gorilla/sessions"
"github.com/gorilla/mux"
)
var store = sessions.NewCookieStore([]byte("keep-it-secret"))
func handler(w http.ResponseWriter,r *http.Request){
session,err := store.Get(r,"session-name")
if err != nil{
http.Error(w,err.Error(),http.StatusInternalServerError)
return
}
session.Values["abc"] = "cba"
session.Values[111] = 222
session.Save(r,w)
}
func main(){
router := mux.NewRouter()
http.Handle("/",router)
router.HandleFunc("/",handler)
http.ListenAndServe(":9090",nil)
}
在这里,我们使用NewCookieStore初始化了一个session存储器,之后,我们在handler内调用了store的Get方法获取一个session-name的session,如果存在,直接访问,不存在,就会被创建.
当我们创建了一个session,一般通过values属性进行赋值,values属性是一个Go map对象,底层实现是哈希表,可以使用键值对的方式进行操作.
8.3 Flash消息
Gorilla从Ruby的flash message得来的设计方法,一个flash消息就是一个简单的session值,直到被读取完成.我们一般使用flash消息在请求之间存储临时数据,比如成功或者错误的消息,主要为了避免重复定义.
可以使用AddFlash方法来访问flash消息
package main
import (
"net/http"
"github.com/gorilla/sessions"
"github.com/gorilla/mux"
"fmt"
"time"
)
var store = sessions.NewCookieStore([]byte("keep-it-secret"))
func handler(w http.ResponseWriter,r *http.Request){
session,err := store.Get(r,"session-n")
if err != nil{
http.Error(w,err.Error(),http.StatusInternalServerError)
return
}
if flashes := session.Flashes();len(flashes)>0{
for f := range flashes{
fmt.Println(flashes[f])
}
}else {
session.AddFlash("Flash! ahah,savior of the universe!"+time.Now().String())
}
session.Save(r,w)
}
func main(){
router := mux.NewRouter()
http.Handle("/",router)
router.HandleFunc("/",handler)
http.ListenAndServe(":9090",nil)
}
9.日志打印
log包函数可以打印信息到标准设备,自定义文件或者其它实现了io.Writer接口的地方.目前,我们都是打印到 stdout,在真实的程序中一般不是这样.
在下面例子中,会把消息打印到notices.log,warnings.log,error.log文件中
package main
import (
"log"
"os"
)
var (
Notice *log.Logger
Warning *log.Logger
Error *log.Logger
)
func main(){
noticeFile,err := os.OpenFile("notices.log",os.O_RDWR|os.O_APPEND,0660)
defer noticeFile.Close()
if err != nil{
log.Fatal(err)
}
warnFile,err := os.OpenFile("warnings.log",os.O_RDWR|os.O_APPEND,0660)
defer warnFile.Close()
if err != nil{
log.Fatal(err)
}
errorFile,err := os.OpenFile("error.log",os.O_RDWR|os.O_APPEND,0660)
defer errorFile.Close()
if err != nil{
log.Fatal(err)
}
Notice = log.New(noticeFile,"NOTICE",log.Ldate|log.Ltime)
Notice.SetOutput(noticeFile)
Notice.Println("This is basically F.Y.I")
Warning = log.New(warnFile,"WARNING",log.Ldate|log.Ltime)
Warning.SetOutput(warnFile)
Warning.Println("Perhaps this needs your attention?")
Error = log.New(errorFile,"ERROR",log.Ldate|log.Ltime)
Error.SetOutput(errorFile)
Error.Println("You REALLY should fix this!")
}
打印web请求路径
改造上面RESTful的小例子:打印每次请求的路径,方法,访问的方法,消耗的时间
//logger.go
package main
import (
"net/http"
"time"
"log"
)
func Logger(inner http.Handler,name string) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter,r *http.Request){
startTime := time.Now()
inner.ServeHTTP(w,r)
log.Printf("%s\t%s\t%s\t%s\t",r.Method,r.RequestURI,name,time.Since(startTime))
})
}
//router.go
package main
import (
"github.com/gorilla/mux"
"net/http"
)
func NewRouter() *mux.Router{
router := mux.NewRouter().StrictSlash(true)
for _,route := range routes{
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler,route.Name)
router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(route.HandlerFunc)
}
return router
}
我们使用了中间件的方式,打印了每次请求的信息.