Go中HTTP协议客户端实现
Go语言标准库内置了net/http包,涵盖了HTTP客户端和服务端具体的实现方式。内置的net/http包提供了最简洁的HTTP客户端实现方式,无须借助第三方网络通信库,就可以直接使用HTTP中用得最多的GET和POST方式请求数据。
func HttpClientA() {
//创建一个客户端
client := http.Client{}
//创建一个请求
request, err := http.NewRequest("GET", "https://cn.bing.com/", nil)
checkErr(err) //检查err防止崩溃--匿名函数--闭包
//cookie信息
cookieInfo := &http.Cookie{
Name: "userName",
Value: "Jobs",
}
//添加cookie
request.AddCookie(cookieInfo)
//发送请求--返回响应结果
response, err := client.Do(request)
checkErr(err)
//设置请求头
request.Header.Set("Accept-Language", "zh-cn")
defer response.Body.Close()
fmt.Println("header--", request.Header)
fmt.Println("状态码--", response.StatusCode)
if response.StatusCode == 200 {
fmt.Println("请求成功")
checkErr(err)
} else {
fmt.Println("请求失败", response.StatusCode)
}
}
// 异常检查
func checkErr(err error) {
defer func() {
//程序可能会返回error
if msg, ok := recover().(error); ok {
fmt.Println("出现异常--", msg.Error())
}
}()
if err != nil {
panic(err)
}
}
func main() {
HttpClientA()
httpB()
}
func httpB() {
client := http.Client{}
resp, err := client.Get("https://www.runoob.com/go/go-environment.html")
checkErr(err)
if resp.StatusCode == 200 {
fmt.Println("请求成功--菜鸟")
//
defer resp.Body.Close()// Close ensures that the body has been fully read
}
}
聚合代码Demo
func HttpClientA() {
//创建一个客户端
client := http.Client{}
//创建一个请求
request, err := http.NewRequest("GET", "https://cn.bing.com/", nil)
checkErr(err) //检查err防止崩溃--匿名函数--闭包
//cookie信息
cookieInfo := &http.Cookie{
Name: "userName",
Value: "Jobs",
}
//添加cookie
request.AddCookie(cookieInfo)
//发送请求--返回响应结果
response, err := client.Do(request)
checkErr(err)
//设置请求头
request.Header.Set("Accept-Language", "zh-cn")
defer response.Body.Close()
fmt.Println("header--", request.Header)
fmt.Println("状态码--", response.StatusCode)
if response.StatusCode == 200 {
fmt.Println("请求成功")
checkErr(err)
} else {
fmt.Println("请求失败", response.StatusCode)
}
}
// 异常检查
func checkErr(err error) {
defer func() {
//程序可能会返回error
if msg, ok := recover().(error); ok {
fmt.Println("出现异常--", msg.Error())
}
}()
if err != nil {
panic(err)
}
}
func main() {
HttpClientA()
httpB()
httpP()
httpC()
httpPostM()
PostM()
}
func httpB() {
client := http.Client{}
resp, err := client.Get("https://www.runoob.com/go/go-environment.html")
checkErr(err)
if resp.StatusCode == 200 {
fmt.Println("请求成功--菜鸟")
//
defer resp.Body.Close() // Close ensures that the body has been fully read
}
}
func httpP() {
client := http.Client{}
resp, err := client.Get("http://localhost:7000/showmsg")
if err != nil {
return
}
if resp.StatusCode == 200 {
fmt.Println("请求成功")
bytes := make([]byte, 1024)
resp.Body.Read(bytes)
fmt.Println("返回的信息--", string(bytes))
}
defer resp.Body.Close()
}
func httpC() {
client := http.Client{}
request, err := http.NewRequest("GET", "http://localhost:7000/showmsg", nil)
checkError(err)
cookie := &http.Cookie{
Name: "cookie",
Value: "Steve Jobs",
}
request.AddCookie(cookie)
request.Header.Set("key", "value")
response, err := client.Do(request)
checkError(err)
if response.StatusCode == 200 {
fmt.Println("请求成功--")
fmt.Println("header--", response.Header)
}
defer response.Body.Close()
}
func checkError(err error) {
if err != nil {
panic(err) //如果请求失败,则抛异常
}
//处理异常
defer func() {
if msg, ok := recover().(error); ok {
fmt.Println("请求出现错误--错误信息", msg.Error())
}
}()
}
// post请求
func httpPostM() {
client := http.Client{}
//发送的数据
m := make(url.Values)
m["name"] = []string{"小明"}
m["age"] = []string{"18"}
resp, err := client.PostForm("http://localhost:7000/getStu", m)
checkError(err)
if resp.StatusCode == 200 {
bytes := make([]byte, 1024)
resp.Body.Read(bytes)
fmt.Println(string(bytes))
}
defer resp.Body.Close()
}
func PostM() {
values := url.Values{
"name": {"小花", "小红"},
}
//将参数转化成body
reader := strings.NewReader(values.Encode())
resp, err := http.Post("http://localhost:7000/getStu", "application/x-www-form-urlencoded", reader)
checkError(err)
if resp.StatusCode == 200 {
fmt.Println("请求成功啦")
bytes := make([]byte, 64)
resp.Body.Read(bytes)
fmt.Println(string(bytes))
}
defer resp.Body.Close()
}
java项目结构–
Http协议服务端实现
实现服务端
实现HTTP服务端就是能够启动Web服务,相当于搭建起了一个Web服务器。这样客户端就可以通过网页请求来与服务器端进行交互。
使用http.FileServer()
这种方式只提供静态的文件访问;
使用ListenAndServe(addr string, handler Handler) 启动web服务,绑定监听的http端口,
第二个参数表示提供文件访问服务的HTTP处理器Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler是一个接口,接口中有ServeHTTP(ResponseWriter, *Request),只要实现了这个方法就实现了 Handler接口
FileServer 返回一个handler,
FileServer()的参数是FileSystem接口,可使用http.Dir()来指定服务端文件所在的路径。如果该路径中有index.html文件,则会优先显示html文件,否则会显示文件目录。
代码Demo
// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// To use the operating system's file system implementation,
// use http.Dir:
//
// http.Handle("/", http.FileServer(http.Dir("/tmp")))
//
// To use an fs.FS implementation, use http.FS to convert it:
//
// http.Handle("/", http.FileServer(http.FS(fsys)))
func FileServer(root FileSystem) Handler {
return &fileHandler{root}
}
开启一个简单的服务端
http.ListenAndServe
func ServerA() {
//如果路径里有index.html,则会优先显示这个,否则会显示文件目录
http.ListenAndServe(":7000", http.FileServer(http.Dir("./mkdir")))
}
func ServerB() {
server := http.FileServer(http.Dir("./")) //Dir实现了FileSystem
//addr如果为"" 则使用默认的 localhost 或127.0.0.1 不带端口号
err := http.ListenAndServe("", server)
//err := http.ListenAndServe("127.0.0.1:8000", server)
if err != nil {
fmt.Println("请求失败")
}
}
ListenAndServe的第一个参数是监听的端口
第二个参数是Handler即处理请求的Handler
http.FileServer()返回的是一个Handler,当然我们也可以自己实现Handler;
请求:localhost:7000
ServeHTTP()方法有两个参数,其中第一个参数是ResponseWriter类型,包含了服务器端给客户端的响应数据。服务器端往ResponseWriter写入了什么内容,浏览器的网页源码就是什么内容。第二个参数是一个 *Request指针,包含了客户端发送给服务器端的请求信息(路径、浏览器类型等)。
路由功能的实现:
路由功能只需要重写http.HandleFunc,其核心的处理逻辑依旧是ServeHTTP()即实现Handler接口;
使用http.HandleFunc()方法
代码Demo
func main() {
//绑定路径去触发方法
//注册网络服务的路由
//底层是:DefaultServeMux.HandleFunc(pattern, handler)
http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
if request.Method == "GET" {
writer.WriteHeader(200)
byt := []byte{'1', '2'}
writer.Write(byt)
}
})
//http.HandleFunc("/index",redir)
err := http.ListenAndServe("localhost:7001", nil)
checkError(err)
}
如果http.ListenAndServer()的第二个参数为nil,那么表示服务端采用默认的http.DefaultServeMux进行分发处理。
所以我们可以通过自定义ServeMux来实现自定义转发逻辑;
使用http.NewServeMux()方法
http. NewServeMux ()的作用是注册网络访问的多路路由。因为它采用的是自定义的多路由分发任务方式,所以称之为自定义多路由分发服务。
源码:
func NewServeMux() *ServeMux { return new(ServeMux) }
ServeMux是一个结构体
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
服务器端获取客户端请求数据
就是启动一个服务端,监听一个addr,重写Handler的ServeHTTP(ResponseWriter, *Request),这里loginActionHandler参数跟ServeHTTP()参数ResponseWriter, *Request一致
get请求参数
// 判断get请求参数
// ResponseWriter, *Request
func loginActionHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("路径", r.URL.Host)
bytes := make([]byte, 100)
r.Body.Read(bytes)
fmt.Println("body--", string(bytes))
r.Header.Set("header", "v")
println(r.ProtoMajor)
println(r.ProtoMinor)
if r.Method == "GET" && r.ParseForm() == nil {
username := r.FormValue("username")
pwd := r.FormValue("pwd")
fmt.Println(username)
fmt.Println(pwd)
if len(username) < 4 || len(username) > 10 {
w.Write([]byte("用户名不合法"))
}
if len(pwd) < 6 || len(pwd) > 16 {
w.Write([]byte("密码不符合规范"))
}
http.Redirect(w, r, "/list", http.StatusFound)
return
} else {
w.Write([]byte("请求方式不对"))
return
}
w.Write([]byte("登陆失败"))
}
func main() {
http.HandleFunc("/login", loginActionHandler)
http.ListenAndServe("localhost:7000", nil)
}
这样请求携带过来的信息 可以通过*Request 带过来—
header
post方式传递的参数
post传参分为以下几种情况:
post传表单信息—Content-Type=application/x-www-form-urlencoded。
传文件—Content-Type=multipart/form-data。
func posthand(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
//如果是post请求
if r.Method == "POST" && r.ParseForm() == nil {
username := r.PostFormValue("username")
pwd := r.PostForm.Get("pwd")
fmt.Println(username)
fmt.Println(pwd)
bytes := make([]byte, 8)
rand.NewSource(time.Now().UnixMilli())
for i := 0; i < len(bytes); i++ {
bytes[i] = byte(rand.Intn(10))
}
encrypt, _ := Encrypt(pwd, bytes)
//保存登录信息
logininfo := username + ":" + encrypt
fmt.Println(logininfo)
cookie := &http.Cookie{
Name: "logininfo",
Value: logininfo,
}
r.AddCookie(cookie)
http.Redirect(w, r, "http://localhost:7001/userinfo?username="+username+"&pwd="+pwd, 302)
return
} else {
w.Write([]byte("请求方式错误"))
return
}
w.Write([]byte("注册失败"))
}
func ZeroPadding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{0}, padding)
return append(ciphertext, padtext...)
}
func ZeroUnPadding(origData []byte) []byte {
return bytes.TrimFunc(origData,
func(r rune) bool {
return r == rune(0)
})
}
//加密
func Encrypt(text string, key []byte) (string, error) {
src := []byte(text)
block, err := des.NewCipher(key)
if err != nil {
return "", err
}
bs := block.BlockSize()
src = ZeroPadding(src, bs)
if len(src)%bs != 0 {
return "", errors.New("Need a multiple of the blocksize")
}
out := make([]byte, len(src))
dst := out
for len(src) > 0 {
block.Encrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
return hex.EncodeToString(out), nil
}
//解密
func Decrypt(decrypted string, key []byte) (string, error) {
src, err := hex.DecodeString(decrypted)
if err != nil {
return "", err
}
block, err := des.NewCipher(key)
if err != nil {
return "", err
}
out := make([]byte, len(src))
dst := out
bs := block.BlockSize()
if len(src)%bs != 0 {
return "", errors.New("crypto/cipher: input not full blocks")
}
for len(src) > 0 {
block.Decrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
out = ZeroUnPadding(out)
return string(out), nil
}
func main() {
http.HandleFunc("/login", posthand)
http.ListenAndServe("localhost:7000", nil)
}
未完待续