Go 原生支持 http,直接使用 import("net/http")
即可,http 服务性能和 nginx 非常接近,都具备高并发支持的能力,代码实现起来较为简单。
1. 服务器配置
package main
import (
"fmt"
"net/http"
)
//业务请求相应处理
func hello(res http.ResponseWriter, req *http.Request) {
fmt.Println("hello") //提交给服务器
fmt.Fprintln(res, "<h1>welcome China</h1>")
}
func test(res http.ResponseWriter, req *http.Request) {
fmt.Println("hello") //提交给服务器
fmt.Fprintln(res, "<h1>welcome testweb</h1>")
}
func main() {
//路由到指定位置,跳转相关函数处理
http.HandleFunc("/", hello)
http.HandleFunc("/test", test)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
验证结果如下:
2. 客户端配置
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
//http客户端配置
func main() {
/*
http.Get()
传入值为 url,是string类型
返回值有两个,
一个是请求,结构体类型,该结构体的Body字段是一个接口
第二个是err
*/
//在Get函数中,放入完整合格域名(FQDN)
res, err := http.Get("https://www.baidu.com/")
if err != nil {
fmt.Println("获取信息失败,err:", err)
return
}
/*
接收 io.reader,这是一个接口
返回字节切片和 err
*/
//response结构体中读取信息
buf, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("读取信息失败,err:", err)
return
}
fmt.Println(string(buf))
}
3. 请求方法
-
Get 请求:
提交的数据都是追加在URL之后,限制点是URL的长度不能超过8K,不适用于提交量大的数据,适用于请求数据,get请求从服务器中读取资源,以明文形式发送请求 -
Post 请求:
数据存放在包中,数据量不受限制,可以提交上传量大的数据信息,post用于更新服务器资源 -
Put 请求:
用于在服务器上创建资源 -
Delete请求:
用户在服务器上删除资源 -
Head请求:
向服务器请求头部信息,用于监控服务状态
示例:
package main
import (
"fmt"
"net/http"
)
//请求头部信息
var url []string = []string{
"https://www.baidu.com/",
"https://www.taobao.com/",
"https://www.google.com/",
}
func main() {
for _, v := range url {
req, err := http.Head(v)
if err != nil {
fmt.Println("获取请求失败,err", err)
return
}
fmt.Printf("来自%s的网站,状态是%s\n", v, req.Status)
}
}
使用自建客户端进行超时时间优化
package main
import (
"fmt"
"net"
"net/http"
"time"
)
//请求头部信息
var url []string = []string{
"https://www.baidu.com/",
"https://www.taobao.com/",
"https://www.google.com/",
}
func main() {
for _, v := range url {
//自建client做测试,优化超时时间
client := http.Client{
Transport: &http.Transport{
//连接服务端
Dial: func(network, addr string) (net.Conn, error) {
//设置超时时间2秒
timeout := time.Second * 2
return net.DialTimeout(network, addr, timeout)
},
},
}
//使用自建客户端测试
req, err := client.Head(v)
if err != nil {
fmt.Println("获取请求失败,err", err)
return
}
fmt.Printf("来自%s的网站,状态是%s\n", v, req.Status)
}
}
常见的状态码
http.StatusContinue =100客户端上传数据需要请求服务端同意才可以上传显示的状态码
http.StatusOK= 200成功连接访问
http.StatusFound = 302页面跳转的状态码
http.StatusBadRequest = 400非法请求,服务端无法解析
http.StatusUnauthorized = 401 权限受限,未通过
http.StatusForbidden = 403禁止访问
http.StatusNotFound = 404请求页面不存在
http.StatusInternalServerError = 500服务器内部错误
表单处理业务
package main
import (
"fmt"
"io"
"net/http"
"os"
)
//定义页面表单html
const form = `<html><head></head><body>
<form action="#" method="post" name="bar">
<input type="test" name="in"/>
<input type="test" name="in"/>
<input type="submit" value="submit"/>
</form></body></html>`
//业务请求相应处理
func hello(res http.ResponseWriter, req *http.Request) {
fmt.Println("hello") //提交给服务器
fmt.Fprintln(res, "<h1>welcome China</h1>")
}
//form表单处理
func formServer(res http.ResponseWriter, req *http.Request) {
//头部申明
res.Header().Set("Content-Type", "text/html")
//类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
//读取第一个框体内容给respomse做页面回应
io.WriteString(os.Stdout, req.Form["in"][0])
io.WriteString(res, "\n")
io.WriteString(res, req.FormValue("in"))
}
}
func main() {
//路由到指定位置,跳转相关函数处理
http.HandleFunc("/test1", hello)
http.HandleFunc("/test2", formServer)
if err := http.ListenAndServe("127.0.0.1:8088", nil); err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
4. panic宕机恢复
web 服务为了防止 goroutine 运行 panic 而终止程序,提出了宕机恢复解决方案。
-
Recover
是一个 Go 语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer
中有效,在正常的执行过程中,调用 recover 会返回nil
并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。 -
通常来说,不应该对进入 panic 宕机的程序做任何处理,但有时,需要我们可以从宕机中恢复,至少我们可以
在程序崩溃前,做一些操作,举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭,如果不做任何处理,会使得客户端─直处于等待状态,如果web服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。 -
在其他语言里,宕机往往以异常的形式存在,底层抛出异常,上层逻辑通过
try/catch机制
捕获异常,没有被捕获的严重异常会导致宕机,捕获的异常可以被忽略,让代码继续运行。 -
Go语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover的宕机恢复机制就对应其他语言中的
try/catch机制
-
panic和recover的组合有如下特性:
有 panic 没 recover,程序宕机;
有 panic 也有 recOver,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
//异常捕捉
package main
import (
"fmt"
"io"
"log"
"net/http"
)
//页面表单
const form = `<html><head><title>web</title></head><body>
<form action="#" method="post" name="bar">
<input type="text" name="in"/>
<input type="text" name="in"/>
<input type="submit" name="提交">
</form></body></html>`
//HTTP服务端配置
//test1业务请求响应处理
func hello(res http.ResponseWriter, req *http.Request) {
fmt.Fprintln(res, "<h1>welcome</h1>") //客户端访问浏览器显示
panic("test1 panic")
}
//form表单处理,使用ResponseWriter在浏览器输出
func formServer(res http.ResponseWriter, req *http.Request) {
//头部声明,表明传的是html的信息
res.Header().Set("Content-Type", "text/html")
//提交类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
/*
WriteString第一个参数是一个接口,所以不仅可以输出到浏览器,还可以将读取的信息输出到任意地方
也可以将内容输出到控制台 os.stdout,也可以写入到文件等
*/
//读取第一个框体内容给response 做页面回应.连个框体名字都是in,
//如果读取第二个,则把下标替换为1
io.WriteString(res, req.Form["in"][0])
//两种方式输出,第一个框体中的信息提交后将会输出两次,第二个框体没有输出
io.WriteString(res, "\n")
io.WriteString(res, req.FormValue("in"))
}
}
func main() {
//将要捕获的函数,放入到异常捕捉函数里
http.HandleFunc("/test1", logPanic(hello))
http.HandleFunc("/test2", logPanic(formServer))
err := http.ListenAndServe("127.0.0.1:8088", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
//宕机恢复recovere,注意,recover 使用在defer中
//将函数http.HandlerFunc作为值传入,返回值也是
func logPanic(handle http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//宕机恢复,这里使用匿名函数,recover 必须在defer 里使用
defer func() {
if x := recover(); x != nil {
log.Printf("%v,捕捉异常:%v", r.RemoteAddr, x)
}
}()
handle(w, r)
}
}
输出结果如下:
5. 模板
模板是用于动态生成页面或者用于代码生成器的编写。
- 示例
main.go
package main
import (
"fmt"
"os"
"text/template"
)
type Persion struct {
Name string
Age int
Title string
}
//映射key:string value不限制类型
// var arr map[string]interface{}
//template模板引用
func main() {
t, err := template.ParseFiles("index.html")
if err != nil {
fmt.Println("模板读取失败,err:", err)
return
}
//实例Persion
persion := Persion{Name: "tom", Age: 20, Title: "个人网站"}
if err := t.Execute(os.Stdout, persion); err != nil {
fmt.Println("输出错误,err:", err.Error())
}
}
index.html
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<h2>{{.Name}}</h2>
<h2>{{.Age}}</h2>
</body>
</html>
页面展示
package main
import (
"fmt"
"net/http"
"text/template"
)
var myTemplate *template.Template
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("模板加载失败,err", err)
return
}
return
}
//业务请求相应处理
func userinfo(res http.ResponseWriter, req *http.Request) {
//参数结构体
type Person struct {
Name string
Age int
Title string
}
p := Person{Name: "json", Age: 20, Title: "智慧城市"}
myTemplate.Execute(res, p)
}
func main() {
//路由到指定位置,跳转相关函数处理
initTemplate("index.html")
http.HandleFunc("/user/info", userinfo)
// http.HandleFunc("/test2", formSever)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
输出到文本中
package main
import (
"fmt"
"net/http"
"os"
"text/template"
)
var myTemplate *template.Template
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("模板加载失败,err", err)
return
}
return
}
//业务请求相应处理
func userinfo(res http.ResponseWriter, req *http.Request) {
//参数结构体
type Person struct {
Name string
Age int
Title string
}
p := Person{Name: "json", Age: 20, Title: "智慧城市"}
//写入文件
file, err := os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
fmt.Println("文件加载失败,err", err)
return
}
myTemplate.Execute(file, p)
//写入浏览器
myTemplate.Execute(res, p)
}
func main() {
//路由到指定位置,跳转相关函数处理
initTemplate("index.html")
http.HandleFunc("/user/info", userinfo)
// http.HandleFunc("/test2", formSever)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
逻辑模板
package main
import (
"fmt"
"net/http"
"os"
"text/template"
)
var myTemplate *template.Template
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("模板加载失败,err", err)
return
}
return
}
//业务请求相应处理
func userinfo(res http.ResponseWriter, req *http.Request) {
//参数结构体
type Person struct {
Name string
Age int
Title string
}
p := Person{Name: "json", Age: 40, Title: "个人网站"}
//写入文件
file, err := os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
fmt.Println("文件加载失败,err", err)
return
}
myTemplate.Execute(file, p)
//写入浏览器
myTemplate.Execute(res, p)
}
func main() {
//路由到指定位置,跳转相关函数处理
initTemplate("index.html")
http.HandleFunc("/user/info", userinfo)
// http.HandleFunc("/test2", formSever)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
index.html
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
{<h1>Persion Template</h1>
{{if gt .Age 30}}
<h2>this is old man,姓名:{{.Name}},年龄:{{.Age}}</h2>
{{else}}
<h2>this is young man,姓名:{{.Name}},年龄:{{.Age}}</h2>
{{end}}
</body>
</html>