http 编程
Go 原生支持http:
import "net/http"
Go 的http服务性能和nginx比较接近:
就是说用Go写的Web程序上线,程序前面不需要再部署nginx的Web服务器,这里省掉的是Web服务器。如果服务器上部署了多个Web应用,还是需要反向代理的,一般这也是nginx或apache。
几行代码就可以实现一个web服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package main import ( "fmt" "net/http" ) func Hello(w http.ResponseWriter, r *http.Request) { fmt .Println(*r) fmt .Fprintf(w, "Hello World" ) } func main() { http.HandleFunc( "/" , Hello) err := http.ListenAndServe( "0.0.0.0:8000" , nil) if err != nil { fmt .Println( "http Listen failed" ) } } |
http client
http 常见的请求方法:
- Get请求
- Post请求
- Put请求
- Delete请求
- Head请求
Get 请求
使用Get请求网站的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { res, err := http.Get( "http://edu.51cto.com" ) if err != nil { fmt .Println( "http get ERRPR:" , err) return } data, err := ioutil.ReadAll(res.Body) if err != nil { fmt .Println( "get data ERROR:" , err) return } fmt .Println(string(data)) } |
Head请求
Head请求只返回响应头。如果只想要获取一些状态信息的话,可以用Head请求。这样避免返回响应体,响应体的数据是比较多的,适合做监控。Head请求的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main import ( "fmt" "net/http" ) var urls = []string{ "http://×××w.baidu.com" , "http://×××w.google.com" , "http://×××w.sina.com.cn" , "http://×××w.163.com" , } func main() { for _, v := range urls { resp, err := http.Head( v ) if err != nil { fmt .Println( "Head request ERROR:" , err) continue } fmt .Println(resp.Status) } } |
http 常见状态码
http.StatusContinue = 100
http.StatusOK = 200
http.StatusFound = 302 跳转
http.StatusBadRequest = 400 非法请求
http.StatusUnanthorized = 401 没有权限
http.StatusForbidden = 403 禁止访问
http.Status.NotFound = 404 页面不存在
http.StatusInternalServerError = 500 内部错误
处理form表单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package main import ( "fmt" "io" "net/http" ) const form = ` <html> <body> <form action= "#" method= "post" name= "bar" > <input type = "text" name= "in" /> <input type = "text" name= "in" /> <input type = "submit" value= "Submit" /> < /form > < /body > < /html >` func FormServer(w http.ResponseWriter, request *http.Request) { w.Header().Set( "content-Type" , "text/html" ) switch request.Method { case "GET" : io.WriteString(w, form) case "POST" : request.ParseForm() io.WriteString(w, request.Form[ "in" ][0]) // 注意上面的2个input的name是一样的 io.WriteString(w, request.Form[ "in" ][1]) // 所以这是一个数组 io.WriteString(w, "</br>" ) io.WriteString(w, request.FormValue( "in" )) // 一般去一个值,就用这个方法 } } func main() { http.HandleFunc( "/form" , FormServer) if err := http.ListenAndServe( ":8000" , nil); err != nil { fmt .Println( "监听端口ERROR:" , err) } } |
panic 处理
如果处理函数里有panic,会导致整个程序崩溃,所以要 defer revoer()
来处理 panic。在处理函数开头defer一个匿名函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func FormServer(w http.ResponseWriter, request *http.Request) { // 增加一个defer来处理panic defer func() { if x := recover(); x != nil { log.Println(request.RemoteAddr, "捕获到异常:" , x) } }() // 原本的处理函数的内容 w.Header().Set( "content-Type" , "text/html" ) switch request.Method { case "GET" : io.WriteString(w, form) case "POST" : request.ParseForm() io.WriteString(w, request.FormValue( "in" )) // 一般去一个值,就用这个方法 } // 搞个panic出来 zero := 0 tmp := 1 / zero io.WriteString(w, string(tmp)) } |
优化统一处理
按照上面的做法,要在每个处理函数的开头都加上panic的处理。由于每个处理函数的panic处理方法都一样,所以可以写一个自定义的处理函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 自定义的panic处理的函数 func logPanics(handle http.HandlerFunc) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { defer func() { if x := recover(); x != nil { log.Println(request.RemoteAddr, "捕获到异常:" , x) } }() // 上面先处理panic,再接着下面调用业务逻辑 handle(writer, request) } } func main() { // http.HandleFunc( "/form" , FormServer) // 修改调用处理函数的方法 http.HandleFunc( "/form" , logPanics(FormServer)) // 把处理函数传给自己写的封装了panic处理的函数里 if err := http.ListenAndServe( ":8000" , nil); err != nil { fmt .Println( "监听端口ERROR:" , err) } } |
原本直接调用处理函数。现在调用自定义的函数,把处理函数传进去。在自定义的函数里先加载defer,然后再调用执行原本的处理函数。逻辑很简单,就是把处理函数作为参数传给自定义的函数,在自定义的函数里再调用处理函数。在自定义的函数里写上defer,这样就相当于所有的处理函数都有defer了。
模板
使用模板需要用到 "text/template" 包。然后调用模板的t.Execute()方法输出。
替换
先准备一个简单的模板:
1
2
|
<p>Hello {{.Name}}< /p > <p>Age: {{.Age}}< /p > |
然后在Go里使用模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package main import ( "fmt" "os" "text/template" ) type Person struct { Name string Age int } func main() { t, err := template.ParseFiles( "index.html" ) if err != nil { fmt .Println( "模板解析异常:" , err) return } p := Person{ "Bob" , 32} if err := t.Execute(os.Stdout, p); err != nil { fmt .Println( "模板加载数据异常:" , err) } } /* 执行结果 PS H:\Go\src\go_dev\day10\http\use_template> go run main.go <p>Hello Bob< /p > <p>Age: 32< /p > PS H:\Go\src\go_dev\day10\http\use_template> */ |
如果直接用 {{.}} 不加字段名的话,就是输出结构体打印的效果。
输出到浏览器里
要输出到浏览器里,只需要在 t.Execute(os.Stdout, p)
里,把原本输出到终端换成输出到处理函数的 w http.ResponseWriter 类型,就好了。
html模板的内容不变,下面是go的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package main import ( "fmt" "net/http" "text/template" ) func Hello(w http.ResponseWriter, r *http.Request) { fmt .Fprintf(w, "Hello World" ) } type Person struct { Name string Age int } func Index(w http.ResponseWriter, r *http.Request) { p := Person{ "Cara" , 18} t, err := template.ParseFiles( "index.html" ) if err != nil { fmt .Println( "加载模板ERROR:" , err) return } t.Execute(w, p) } func main() { http.HandleFunc( "/" , Hello) http.HandleFunc( "/index" , Index) err := http.ListenAndServe( "0.0.0.0:8000" , nil) if err != nil { fmt .Println( "http Listen failed" ) } } |
判断
用法示例:
1
2
3
4
5
6
7
|
<body> {{ if gt .Age 18}} <p>已成年< /p > {{ else }} <p>未成年< /p > {{end}} < /body > |
更多判断逻辑:
not 非
{{if not .condition}}
{{end}}
and 与
{{if and .condition1 .condition2}}
{{end}}
or 或
{{if or .condition1 .condition2}}
{{end}}
eq 等于
{{if eq .var1 .var2}}
{{end}}
ne 不等于
{{if ne .var1 .var2}}
{{end}}
lt 小于
{{if lt .var1 .var2}}
{{end}}
le 小于等于
{{if le .var1 .var2}}
{{end}}
gt 大于
{{if gt .var1 .var2}}
{{end}}
ge 大于等于
{{if ge .var1 .var2}}
{{end}}
with 封装
with语句就是创建一个封闭的作用域,在其范围内,{{.}}代表with的变量,而与外面的{{.}}无关,只与with的参数有关:
1
2
3
4
5
|
<body> {{with .Name}} <p>{{.}}< /p > {{end}} < /body > |
上面这样包在 {{with .Var}} 里,with 里的 {{.}} 代表的就是 Var 这个变量。
with 可以封装常数:
1
2
3
|
{{ with "world" }} Now the dot is set to {{ . }} {{ end }} |
循环(遍历)
golang的template支持range循环来遍历map、slice内的内容,在range循环内,还可以使用$设置循环变量,我们可以通过 $i $v 来访问遍历的值。语法为:
1
2
3
|
{{range $i, $ v := .slice}} <li>key: {{ $key }}, value: {{ $value }}< /li > {{end}} |
这是另外一种遍历方式,这种方式无法访问到index或者key的值,需要通过点来访问对应的value:
1
2
3
|
{{range .slice}} {{.field}} {{end}} |
在循环内,点是代表遍历的值。原本使用点来访问的变量,那么在循环内部就要用 $. 来访问。下面的例子表示循环内和循环外 ArticleConten 这个变量访问的方式:
1
2
3
4
|
{{.ArticleContent}} {{range .slice}} {{$.ArticleContent}} {{end}} |
定义变量
模板的参数可以是go中的基本数据类型,如字串,数字,布尔值,数组切片或者一个结构体。在模板中设置变量可以使用 $variable := value。我们在range迭代的过程使用了设置变量的方式。
1
2
|
{{$article := "hello" }} {{$name := .Name}} |
mysql 使用
这里只简单讲了数据的增删改查,所以测试代码前,需要先把数据库准备好。
先创建一个数据库,指定了编码,这样应该可以支持中文:
1
|
CREATE DATABASE 库名 CHARSET "utf8" ; |
然后建2张表:
1
2
3
4
5
6
7
8
9
10
11
12
|
CREATE TABLE person ( user_id int primary key auto_increment, username varchar(260), gender varchar(260), email varchar(260) ); CREATE TABLE place ( country varchar(200), city varchar(200), telcode int ); |
导入数据库驱动
sql 包提供了通用的SQL(或类SQL)数据库接口。
sql 包必须与数据库驱动结合使用。
驱动包需要安装:
go get -u github.com/go-sql-driver/mysql
使用前,先要导入mysql的包:
1
2
3
4
|
import ( "database/sql" _ "github.com/go-sql-driver/mysql" ) |
上面导入了2个包。第一个是sql包,就是我们调用操作数据库用的。
第二个是驱动包,这里前面加了占位符,所以这个包只是引入,但是不使用它。并且如果要操作别的数据库的话,只需要修改驱动包就行了。
连接数据库
构建连接, 格式是:”用户名:密码@tcp(IP:端口)/数据库?charset=utf8” :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package main import ( "fmt" "time" "database/sql" _ "github.com/go-sql-driver/mysql" ) var DB *sql.DB func init() { database, err := sql.Open( "mysql" , "admin:admin123@tcp(192.168.3.103:3306)/Golang_week10" ) if err != nil { fmt .Println( "连接数据库失败:" , err) return } DB = database } func main() { fmt .Println(DB) DB.SetMaxIdleConns(16) // 设置闲置连接数 DB.SetMaxOpenConns(100) // 设置最大连接数 DB.SetConnMaxLifetime(100* time .Second) // 最大连接周期,超过时间的连接就close fmt .Println(DB) } |
插入数据
下面是插入数据,并且再获取id的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 数据库连接的init函数就省略了 func insert() { r, err := DB.Exec( "insert into person(username,gender,email) values(?,?,?)" , "Barry" , "Male" , "Barry@go.net" ) if err != nil { fmt .Println( "插入数据ERROR:" , err) return } fmt .Println(r) id , err := r.LastInsertId() if err != nil { fmt .Println( "获取id ERROR:" , err) return } fmt .Println( id ) } func main() { insert() } |
上面的 values(?,?,?) 里的问号,是占位符,具体的值可以写在后面的参数里。当然如果不用占位符,直接就传1个字符串作为参数也是可以的。
查询
查询单个字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func query() { row := DB.QueryRow( "select username from person where user_id=?" , 1) var name string // 创建变量用于存放查询到的数据 if err := row.Scan(&name); err != nil { fmt .Println( "Scan Failed:" , err) return } fmt .Println(name) } func main() { query() } |
也可以一次查询多个字段或所有字段,查询之前按照表的类型创建结构体,用查询到的数据为结构体赋值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
type Person struct { ID int `db: "user_id" ` Username sql.NullString `db: "username" ` Gender sql.NullString `db: "gender" ` Email sql.NullString `db: "email" ` } func query() { row := DB.QueryRow( "select * from person where user_id=?" , 6) var person = new(Person) // row.scan中的字段必须是按照数据库存入字段的顺序,否则报错 if err := row.Scan(&person.ID, &person.Username, &person.Gender, &person.Email); err != nil { fmt .Println( "Scan Failed:" , err) return } fmt .Println(person) } func main() { query() } |
数据模型,就是上面定义的结构体。这里的类型可以是Go的标准数据类型。但是如果数据库的字段允许为空,并且该字段的值也为空,那么查询后该字段会返回nil。如果是string类型,则无法接收nil,但sql.NullString则可以接收nil值。
另外,结构体里的tag标签在这里没有意义。不过上面的tag标注了该字段在数据库里对应的字段名,可能在别处会有用。
查询多行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
func query() { rows, err := DB.Query( "select * from person where user_id > ?" , 1) defer func() { if rows != nil { rows.Close() } }() if err != nil { fmt .Println( "Query 查询 ERROR:" , err) return } var person = new(Person) for rows.Next() { if err = rows.Scan(&person.ID, &person.Username, &person.Gender, &person.Email); err != nil { fmt .Println( "Scan Failed:" , err) return } fmt .Println(person) } } func main() { query() } |
查询用起来还是不太方法,不过还可以选择其他第三方库,那里会有一些很好的扩展。后面会举例。
其他操作
由于基本都是用SQL的命令进行操作,所以其他操作就不一个一个举例了
update 更新数据
1
|
result, err := DB.Exec( "UPDATE person set email=? where username=?" , "Cara" , <a href= "mailto:Cara@catco.org" >Cara@catco.org< /a >) |
delete 删除数据
1
|
result,err := DB.Exec( "DELETE FROM person where id=?" ,1) |
注意:更新数据不返回LastInsertID,所以result.LastInsertID一直为0。删除数据可以拿到LastInsertID,用法和插入数据里一样。
第三方库 sqlx
sqlx是一个go语言包,在内置database/sql包之上增加了很多扩展,简化数据库操作代码的书写。
由于database/sql接口是sqlx的子集,所有database/sql的用法,在sqlx中一样可以用。不过sqlx还有更多扩展,用起来更方便。
安装:
go get github.com/jmoiron/sqlx
查询 Select() 方法
Select是一个非常省时的扩展。它们把query和非常灵活的scan语法结合起来。Select用来获取结果切片:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 这里的tag标签就有意义了,下面的Select()方法应该就是根据tag标签对号入座的 type Person struct { ID int `db: "user_id" ` Username sql.NullString `db: "username" ` Gender sql.NullString `db: "gender" ` Email sql.NullString `db: "email" ` } func select () { var persons []Person // 这里创建的是存放结构体的切片 if err := DB.Select(&person, "select * from person where userid > ?" , 1); err != nil { fmt .Println( "Select ERROR:" , err) return } fmt .Println(person) } |
Select可以提高编码效率,还有更多扩展。sqlx 号称 golang 数据库开发神器,这里就提一下,等到真正用的时候再去深入学习了。
原文链接:https://studygolang.com/articles/16482