Go 原生支持http:import "net/http"
Go 的http服务性能和nginx比较接近:
就是说用Go写的Web程序上线,程序前面不需要再部署nginx的Web服务器,这里省掉的是Web服务器。如果服务器上部署了多个Web应用,还是需要反向代理的,一般这也是nginx或apache。
一般几行代码就可以实现一个web服务:
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 常见的请求方法:有以下5种方法
1 Get请求 ,2 Post请求,3 Put请求,4 Delete请求,5 Head请求
我们先来看看Get 请求
使用Get请求网站的示例:
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请求的示例:
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表单
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一个匿名函数:
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处理方法都一样,所以可以写一个自定义的处理函数:
// 自定义的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()方法输出。
替换
先准备一个简单的模板:
<p>Hello {{.Name}}</p>
<p>Age: {{.Age}}</p>
然后在Go里使用模板:
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的代码:
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")
}
}
判断
用法示例:
<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的参数有关:
<body>
{{with .Name}}
<p>{{.}}</p>
{{end}}
</body>
上面这样包在 {{with .Var}} 里,with 里的 {{.}} 代表的就是 Var 这个变量。
with 可以封装常数:
{{ with "world"}}
Now the dot is set to {{ . }}
{{ end }}
循环(遍历)
golang的template支持range循环来遍历map、slice内的内容,在range循环内,还可以使用$设置循环变量,我们可以通过 $i $v 来访问遍历的值。语法为:
{{range $i, $v := .slice}}
<li>key: {{ $key }}, value: {{ $value }}</li>
{{end}}
这是另外一种遍历方式,这种方式无法访问到index或者key的值,需要通过点来访问对应的value:
{{range .slice}}
{{.field}}
{{end}}
在循环内,点是代表遍历的值。原本使用点来访问的变量,那么在循环内部就要用 $. 来访问。下面的例子表示循环内和循环外 ArticleConten 这个变量访问的方式:
{{.ArticleContent}}
{{range .slice}}
{{$.ArticleContent}}
{{end}}
定义变量
模板的参数可以是go中的基本数据类型,如字串,数字,布尔值,数组切片或者一个结构体。在模板中设置变量可以使用 $variable := value。我们在range迭代的过程使用了设置变量的方式。
{{$article := "hello"}}
{{$name := .Name}}