http常见请求方法:
1)Get请求。
2)Post请求。
3)Put请求。
4)Delete请求。
5)Head请求。
更详细点:
HTTP/1.0定义了三种请求方法:GET、POST、和HEAD方法。
HTTP/1.1新增了五种方法:OPTIONS,PUT,CONNECT,DELETE和TRACE方法。所以HTTP/1.1协议中共定义了八种方法,来表明Request-URL指定的资源不同的操作方式。
http.StatusContinue = 100
http.StatusOK = 200
http.StatusFound = 302
http.StatusBadRequest = 400
http.StatusUnauthorized = 401
http.StatusForbidden = 403
http.StatusNotFound = 404
http.StatusInternalServerError = 500
详细的http状态码可以参考:HTTP状态码(完整版)。
该案例是一个简单的http服务器案例,使用go原生的http服务器,只需要包含(“net/http”)包即可。
package main
import (
"fmt"
"net/http"
)
//say hello to the world
func sayHello(w http.ResponseWriter, r *http.Request) {
//n, err := fmt.Fprintln(w, "hello world")
_, _ = w.Write([]byte("你好, sayHello!"))
}
func helloTyy(w http.ResponseWriter, r *http.Request) {
//n, err := fmt.Fprintln(w, "hello world")
_, _ = w.Write([]byte("你好, helloTyy!"))
}
func hlloLqq(w http.ResponseWriter, r *http.Request) {
//n, err := fmt.Fprintln(w, "hello world")
_, _ = w.Write([]byte("你好, Lqq!"))
}
func main() {
// 一 使用默认的处理器函数。
// 1. 注册一个处理器函数。
// http可以根据url路由到不同的处理函数。
http.HandleFunc("/", sayHello)
http.HandleFunc("/tyy", helloTyy) // 其他url处理
http.HandleFunc("/lqq", hlloLqq)
// 2. 设置监听的TCP地址并启动服务
// 参数1: TCP地址(IP+Port)
// 参数2: handler handler参数一般会设为nil,此时会使用DefaultServeMux。
err := http.ListenAndServe("0.0.0.0:9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe() error: %v\n", err)
return
}
// 或者:
// 二 自己创建一个处理器函数。
// 1.注册一个处理器函数
// serveMux := http.NewServeMux()
// serveMux.HandleFunc("/", sayHello)
// serveMux.HandleFunc("/tyy", helloTyy) // 其他url处理
// http.HandleFunc("/lqq", hlloLqq)
// // 2. 设置监听的TCP地址并启动服务
// // 参数1 :TCP地址(IP+Port)
// // 参数2 :handler 创建新的*serveMux,不使用默认的
// err := http.ListenAndServe("0.0.0.0:9000", serveMux)
// if err != nil {
// fmt.Printf("http.ListenAndServe() error: %v\n", err)
// return
// }
}
go run http-server.go执行该http服务器,然后在客户端输入127.0.0.1:9000/或者其它url,即可访问到该http服务器。
上面简单讲了使用go作为web服务器的案例,这里会讲述go如果作为http客户端去请求别人的web服务器。
http包提供了很多访问Web服务器的函数,比如http.Get()、http.Post()、http.Head()等,读到的响应报文数据被保存在 Response 结构体中。
Response结构体的定义:
type Response struct {
Status string // 状态描述信息,e.g. "200 OK"
StatusCode int // 状态码,e.g. 200
Proto string // HTTP协议,e.g. "HTTP/1.0"
ProtoMajor int // 主协议版本号,e.g. 1,即上面1.0的1.
ProtoMinor int // 自协议版本号,e.g. 0,即上面1.0的0.HTTP/1.1的话,那么自协议就是1.
Header Header // 响应头
Body io.ReadCloser // 相应体
//...
}
当我们请求别人的web服务器时,服务器发送的响应包体被保存在Body中。可以使用它提供的Read方法来获取数据内容。保存至切片
缓冲区中,拼接成一个完整的字符串来查看。结束的时候,需要调用Body中的Close()方法关闭io。
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// 1. 发送Get请求
// resp, err := http.Get("http://127.0.0.1:9000")
resp, err := http.Get("https://www.baidu.com/")
if err != nil {
fmt.Println("get err:", err)
return
}
// 2. 函数结束时必须关闭Body。
defer resp.Body.Close()
// 3. 读取Get请求获取到的Body信息。
// data byte
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("get data err:", err)
return
}
// 4. 打印request line、head、Body的信息。
fmt.Println("+++++++++++++++++++ request line: ")
fmt.Println(resp.Request.Method, resp.Request.URL, resp.Request.Proto)
fmt.Println()
fmt.Println("+++++++++++++++++++ request head: ")
fmt.Println(resp.Request.Header)
fmt.Println()
fmt.Println("+++++++++++++++++++ request body: ")
fmt.Println(resp.Request.Body)
fmt.Println()
// 5. 打印respose line、head、Body以及整个resp信息。
fmt.Println("+++++++++++++++++++ respose line: ")
fmt.Println(resp.Proto, resp.StatusCode, resp.Status)
fmt.Println()
fmt.Println("+++++++++++++++++++ respose head: ")
fmt.Println(resp.Header)
fmt.Println()
fmt.Println("+++++++++++++++++++ respose body: ")
fmt.Println(string(data))
fmt.Println()
fmt.Println("+++++++++++++++++++ resp: ")
fmt.Println(resp)
}
下图我们主要看响应的部分打印,可以看到,整个resp信息包含了响应行,响应头部,然后绿色部分的应该就是需要ReadAll读取转成字节流数据才能看到的内容。
带参数的Get请求并不难,主要思想是先对裸url即不带参数的url进行解析,然后再将参数拼接在一起。
GET请求的参数需要使用Go语言内置的net/url这个标准库来处理。URL标准库的详细说明请参考:Go 学习笔记(50)— Go 标准库之 net/url(查询转义、查询参数增/删/改/查、解析URL)。
示例我们以上面简单的http服务器案例作为服务器,然后进行带参数的Get请求。
client.go:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func main() {
// 1. 处理请求参数。
params := url.Values{} // Values是一个map类型
params.Set("name", "tyy") // Set的时候会把string转成[]string
params.Set("hobby", "足球")
// 2. 设置请求URL结构体。
// 这一步会利用rawUrl初始化成type URL struct{xxx}结构的内容。
rawUrl := "http://127.0.0.1:9000"
reqURL, err := url.ParseRequestURI(rawUrl)
if err != nil {
fmt.Printf("url.ParseRequestURI()函数执行错误,错误为:%v\n", err)
return
}
fmt.Println("2 reqURL: ", reqURL, "++++++++ rawUrl: ", rawUrl)
// 3. 处理参数,保存在reqURL.RawQuery中。
// Encode方法将 请求参数params 编码为url编码格式("bar=baz&foo=quux"),编码时会以键进行排序.
reqURL.RawQuery = params.Encode()
// 注意打印时,reqURL会自动将rawUrl+reqURL.RawQuery参数的形式打印,
// 所以此时reqURL就是一个带上参数的完整url。这一步最好debug去看才能理解。
fmt.Println("3 params.Encode(), reqURL: ", reqURL, "reqURL.RawQuery: ", reqURL.RawQuery)
// 4. 发送HTTP请求
// 说明: reqURL.String() String将URL重构为一个合法URL字符串。
fmt.Println("4 Get url:", reqURL.String())
resp, err := http.Get(reqURL.String())
if err != nil {
fmt.Printf("http.Get()函数执行错误,错误为:%v\n", err)
return
}
defer resp.Body.Close()
// 5.一次性读取响应的所有内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("ioutil.ReadAll()函数执行出错,错误为:%v\n", err)
return
}
fmt.Println("5 Response: ", string(body))
}
server.go
package main
import (
"fmt"
"net/http"
)
// 响应: http.ResponseWriter
// 请求:http.Request
func myHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := r.URL.Query()
fmt.Println("r.URL: ", r.URL)
fmt.Fprintln(w, "name:", params.Get("name"), "hobby:", params.Get("hobby")) // 回写数据
}
func main() {
http.HandleFunc("/", myHandler)
err := http.ListenAndServe("127.0.0.1:9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe() error: %v\n", err)
return
}
}
client.go:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
// net/http post demo
func main() {
// 1. 构造post请求
url := "http://127.0.0.1:9000/post"
contentType := "application/json"
data := `{"name":"tyy","age":18}`
resp, err := http.Post(url, contentType, strings.NewReader(data))
if err != nil {
fmt.Println("post failed, err:%v\n", err)
return
}
defer resp.Body.Close() // 客户端需要关闭响应体的Body.Close().服务端需要关闭请求体的Body.Close().
// 2. 使用ReadAll读取响应体的内容。
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("get resp failed,err:%v\n", err)
return
}
fmt.Println("StatusCode:", resp.StatusCode)
fmt.Println(string(b))
}
server.go:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func postHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// 1. 判断http请求的类型
fmt.Println("Method ", r.Method)
if r.Method == "POST" {
// 1. 请求类型是application/json时从r.Body读取数据
b, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println("read request.Body failed, err: %v\n", err)
return
}
fmt.Println(string(b))
// 2. 处理完毕,回发消息
answer := `{"status": "ok"}`
w.Write([]byte(answer))
} else {
fmt.Println("can't handle ", r.Method)
w.WriteHeader(http.StatusBadRequest)
}
}
func main() {
http.HandleFunc("/post", postHandler) //浏览器输入http://ip:9000/post会触发该回调函数。
err := http.ListenAndServe("0.0.0.0:9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe() error: %v\n", err)
return
}
}
HEAD请求常常被忽略,但是能提供很多有用的信息,特别是在有限的速度和带宽下。
主要有以下特点:
client.go
package main
import (
"fmt"
"net"
"net/http"
"time"
)
func main() {
url := "http://www.baidu.com"
// 1. 定义一个Client对象,并自定义传输,以设置超时处理。
c := http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
timeout := time.Second * 2
return net.DialTimeout(network, addr, timeout)
},
},
}
// 2. 发送Head请求。
resp, err := c.Head(url)
if err != nil {
fmt.Printf("head %s failed, err:%v\n", url, err)
} else {
fmt.Printf("%s head succ, status:%v\n", url, resp.Status)
}
}
server.go:
以百度为例。
结果:
server.go:
package main
import (
"fmt"
"io"
"net/http"
)
// 定义一个表单html页面,当我们点击Submit按钮时,会触发post请求。
// const form = `
//
//
//
// `
const form = `
`
func HomeServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "/test1 或者/test2")
// io.WriteString(w, "/test1 或者/test2
")
}
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "hello, world
")
}
func FormServer(w http.ResponseWriter, request *http.Request) {
// 1. 设置响应的body的格式是文本。
w.Header().Set("Content-Type", "text/html")
// 2. 处理不同的方法。
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
// 3. 解析表单到成员Form。PostForm实际此时也会有数据。
request.ParseForm()
// 4. 模拟服务器处理表单。
fmt.Println("request.Form[in]:", request.Form["in"]) // 打印请求到来的表单,表单内容被放到map中key=in的字符串切片中。Form、PostForm均是map结构
// 5. 把请求到来的表单内容回发给请求者。
// 请求者的表单会被放到map中key=in的字符串切片中,所以我们取下标0和1即可得到一个key-value数据。
// 因为我们测试,所以只回发一个key-value数据即可。
io.WriteString(w, request.Form["in"][0])
io.WriteString(w, "\n")
io.WriteString(w, request.Form["in"][1]) // go web开发
// var ptr *int
// *ptr = 0x123445 // 模拟异常
}
}
func main() {
http.HandleFunc("/", HomeServer)
http.HandleFunc("/test1", SimpleServer)
http.HandleFunc("/test2", FormServer)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe() error: %v\n", err)
return
}
}
然后在浏览器输入对应的http://127.0.0.1:9000/test2即可。
有时候我们在写http的相关代码时,难免会出现一些问题,但是你目前又没排查出来,将来可能会触发panic导致程序崩溃。
那么我们可以去将某些关键的代码做处理,如果将来触发panic,我们手动去捕捉该错误,防止程序崩溃。
解决的流程:
recover的解释可以看:go语言基础-----09-----异常处理(error、panic、recover)。
完整代码:
package main
import (
"fmt"
"io"
"log"
"net/http"
)
// 定义一个表单html页面,当我们点击Submit按钮时,会触发post请求。
// const form = `
//
//
//
// `
const form = `
`
func HomeServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "/test1 或者/test2")
// io.WriteString(w, "/test1 或者/test2
")
}
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "hello, world
")
}
func FormServer(w http.ResponseWriter, request *http.Request) {
// 1. 设置响应的body的格式是文本。
w.Header().Set("Content-Type", "text/html")
// 2. 处理不同的方法。
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
// 3. 解析表单到成员Form。PostForm实际此时也会有数据。
request.ParseForm()
// 4. 模拟服务器处理表单。
fmt.Println("request.Form[in]:", request.Form["in"]) // 打印请求到来的表单,表单内容被放到map中key=in的字符串切片中。Form、PostForm均是map结构
// 5. 把请求到来的表单内容回发给请求者。
// 请求者的表单会被放到map中key=in的字符串切片中,所以我们取下标0和1即可得到一个key-value数据。
// 因为我们测试,所以只回发一个key-value数据即可。
io.WriteString(w, request.Form["in"][0])
io.WriteString(w, "\n")
io.WriteString(w, request.Form["in"][1]) // go web开发
// 模拟异常
var ptr *int
*ptr = 0x123445
}
}
func main() {
http.HandleFunc("/", HomeServer)
http.HandleFunc("/test1", logPanics(SimpleServer))
http.HandleFunc("/test2", logPanics(FormServer))
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Printf("http.ListenAndServe() error: %v\n", err)
return
}
}
// recover会被触发的条件:1)在defer所处的函数调用(即匿名函数2)。2)与包含defer语句的函数发生错误(即匿名函数1)。
// 所以使用recover基本都会看到两个匿名函数。
func logPanics(handle http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) { // 假设是匿名函数1
defer func() { // 假设是匿名函数2
if x := recover(); x != nil {
log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
}
}()
handle(writer, request)
}
}