Go web开发小知识

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.ResponseWriterhttp.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)
    }
}

结果:

html.png

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>

结果:

result.png

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
}

我们使用了中间件的方式,打印了每次请求的信息.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容