go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理

一 HTTP编程get/post/head

1 HTTP编程

  • 1) Go原生支持http,import(“net/http”)即可。
  • 2) Go的http服务性能和nginx比较接近。
  • 3) 几行代码就可以实现一个web服务。
1.1 HTTP常见请求方法

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指定的资源不同的操作方式。
go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第1张图片

1.2 HTTP常见状态码
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状态码(完整版)。

1.3 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语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第2张图片

2 Client客户端

上面简单讲了使用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。

例如:
go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第3张图片

2.1基本的HTTP/HTTPS请求
2.1.1 不带参数的Get方法示例。
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读取转成字节流数据才能看到的内容。
go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第4张图片

2.1.2 带参数的Get方法示例。

带参数的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
	}
}

运行结果:
go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第5张图片

2.1.3 post方法

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
	}
}

结果:
服务器的打印:
在这里插入图片描述

客户端的打印:
在这里插入图片描述

2.1.4 head方法-client

HEAD请求常常被忽略,但是能提供很多有用的信息,特别是在有限的速度和带宽下。
主要有以下特点:

  • 1、只请求资源的首部;
  • 2、检查超链接的有效性;
  • 3、检查网页是否被修改;
  • 4、多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等。

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即可。
在这里插入图片描述

服务器打印:
在这里插入图片描述

三 panic处理

有时候我们在写http的相关代码时,难免会出现一些问题,但是你目前又没排查出来,将来可能会触发panic导致程序崩溃。

那么我们可以去将某些关键的代码做处理,如果将来触发panic,我们手动去捕捉该错误,防止程序崩溃。

解决的流程:

  • 1)添加一个封装函数,返回值的类型是http.HandlerFunc。
  • 2)在封装的函数中使用defer+recover去捕捉可能发生的错误。

recover的解释可以看:go语言基础-----09-----异常处理(error、panic、recover)。

go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第6张图片

go语言实战-----25-----HTTP编程get/post/head、表单处理、panic处理_第7张图片

完整代码:

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

结果看到,即使发生了错误,程序依然正常运行。并且页面也是能正常返回的,因为错误之前的代码是没有问题的。
在这里插入图片描述

你可能感兴趣的:(Go,go)