• go语言基础---8


    Http请求报文格式分析

    package main
    
    import (
    	"fmt"
    	"net"
    )
    
    func main() {
    
    	//监听
    	listener, err := net.Listen("tcp", ":8000")
    	if err != nil {
    		fmt.Println("listener err", err)
    		return
    	}
    
    	defer listener.Close()
    	//阻塞等待用户的连接
    	conn, err := listener.Accept()
    	if err != nil {
    		fmt.Println("Accept err = ", err)
    		return
    	}
    
    	defer conn.Close()
    
    	//接收客户端的数据
    	buf := make([]byte, 1024*4)
    
    	readSize, err := conn.Read(buf)
    
    	if readSize == 0 { //对方断开,出问题了
    		fmt.Println("Read err = ", err)
    		return
    	}
    
    	fmt.Printf("#%v#", string(buf[:readSize]))
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    #GET / HTTP/1.1 //请求行
    Host: 127.0.0.1:8000
    Connection: keep-alive
    Cache-Control: max-age=0
    sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
    
    #
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    HTTP编程

    go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。使用net/http包,我们可以很方便地编写HTTP客户端或服务端的程序。

    func ListenAndServe(addr string, handler Handler) error
    
    • 1

    ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。
    在这里插入图片描述

    package main
    
    import "net/http"
    
    // HandleConn 第一个参数,给客户端回复数据,req 读取客户端发送的数据
    func HandleConn(w http.ResponseWriter, req *http.Request) {
    	_, err := w.Write([]byte("hello go")) //给客户端回复数据
    	if err != nil {
    		return
    	}
    }
    
    func main() {
    	//HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。
    	//ServeMux的文档解释了模式的匹配机制。
    	//注册处理函数,用户连接,自动调用指定的处理函数
    	//func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
    	http.HandleFunc("/", HandleConn)
    
    	//监听绑定
    	//ListenAndServe监听TCP地址addr,
    	//并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。
    	http.ListenAndServe(":8000", nil)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    http服务器获取客户端的一些信息

    type Request struct {
        // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
        Method string
        // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
        //
        // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
        // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
        // (参见RFC 2616, Section 5.1.2)
        //
        // 在客户端,URL的Host字段指定了要连接的服务器,
        // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
        URL *url.URL
        // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
        Proto      string // "HTTP/1.0"
        ProtoMajor int    // 1
        ProtoMinor int    // 0
        // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
        //	accept-encoding: gzip, deflate
        //	Accept-Language: en-us
        //	Connection: keep-alive
        // 则:
        //	Header = map[string][]string{
        //		"Accept-Encoding": {"gzip, deflate"},
        //		"Accept-Language": {"en-us"},
        //		"Connection": {"keep-alive"},
        //	}
        // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
        // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
        Header Header
        // Body是请求的主体。
        //
        // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
        // Client的Transport字段会负责调用Body的Close方法。
        //
        // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
        // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
        Body io.ReadCloser
        // ContentLength记录相关内容的长度。
        // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
        // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
        ContentLength int64
        // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
        // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
        TransferEncoding []string
        // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
        Close bool
        // 在服务端,Host指定URL会在其上寻找资源的主机。
        // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
        // Host的格式可以是"host:port"。
        //
        // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
        // 如过该字段为"",Request.Write方法会使用URL字段的Host。
        Host string
        // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
        // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
        Form url.Values
        // PostForm是解析好的POST或PUT的表单数据。
        // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
        PostForm url.Values
        // MultipartForm是解析好的多部件表单,包括上传的文件。
        // 本字段只有在调用ParseMultipartForm后才有效。
        // 在客户端,会忽略请求中的本字段而使用Body替代。
        MultipartForm *multipart.Form
        // Trailer指定了会在请求主体之后发送的额外的头域。
        //
        // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
        // (客户端会声明哪些trailer会发送)
        // 在处理器从Body读取时,不能使用本字段。
        // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
        // (如果客户端发送了这些键值对),此时才可以访问本字段。
        //
        // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
        // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
        // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
        // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
        //
        // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
        Trailer Header
        // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
        // 本字段不是ReadRequest函数填写的,也没有定义格式。
        // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
        // 客户端会忽略请求中的RemoteAddr字段。
        RemoteAddr string
        // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
        // (参见RFC 2616, Section 5.1)
        // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
        RequestURI string
        // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
        // 本字段不是ReadRequest函数填写的。
        // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
        // 客户端会忽略请求中的TLS字段。
        TLS *tls.ConnectionState
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    // HandleConn 第一个参数,给客户端回复数据,req 读取客户端发送的数据
    func HandleConn(w http.ResponseWriter, req *http.Request) {
    
    	fmt.Println("r.Method = ", req.Method) //r.Method =  GET
    	fmt.Println("r.URL = ", req.URL)       // /
    	fmt.Println("Header = ", req.Header)
    	fmt.Println("Body = ", req.Body)
    
    	_, err := w.Write([]byte("hello go")) //给客户端回复数据
    	if err != nil {
    		return
    	}
    }
    
    func main() {
    	//HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。
    	//ServeMux的文档解释了模式的匹配机制。
    	//注册处理函数,用户连接,自动调用指定的处理函数
    	//func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
    	http.HandleFunc("/", HandleConn)
    
    	//监听绑定
    	//ListenAndServe监听TCP地址addr,
    	//并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。
    	http.ListenAndServe(":8000", nil)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    http客户端编程

    type Response struct {
        Status     string // 例如"200 OK"
        StatusCode int    // 例如200
        Proto      string // 例如"HTTP/1.0"
        ProtoMajor int    // 例如1
        ProtoMinor int    // 例如0
        // Header保管头域的键值对。
        // 如果回复中有多个头的键相同,Header中保存为该键对应用逗号分隔串联起来的这些头的值
        // (参见RFC 2616 Section 4.2)
        // 被本结构体中的其他字段复制保管的头(如ContentLength)会从Header中删掉。
        //
        // Header中的键都是规范化的,参见CanonicalHeaderKey函数
        Header Header
        // Body代表回复的主体。
        // Client类型和Transport类型会保证Body字段总是非nil的,即使回复没有主体或主体长度为0。
        // 关闭主体是调用者的责任。
        // 如果服务端采用"chunked"传输编码发送的回复,Body字段会自动进行解码。
        Body io.ReadCloser
        // ContentLength记录相关内容的长度。
        // 其值为-1表示长度未知(采用chunked传输编码)
        // 除非对应的Request.Method是"HEAD",其值>=0表示可以从Body读取的字节数
        ContentLength int64
        // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
        TransferEncoding []string
        // Close记录头域是否指定应在读取完主体后关闭连接。(即Connection头)
        // 该值是给客户端的建议,Response.Write方法的ReadResponse函数都不会关闭连接。
        Close bool
        // Trailer字段保存和头域相同格式的trailer键值对,和Header字段相同类型
        Trailer Header
        // Request是用来获取此回复的请求
        // Request的Body字段是nil(因为已经被用掉了)
        // 这个字段是被Client类型发出请求并获得回复后填充的
        Request *Request
        // TLS包含接收到该回复的TLS连接的信息。 对未加密的回复,本字段为nil。
        // 返回的指针是被(同一TLS连接接收到的)回复共享的,不应被修改。
        TLS *tls.ConnectionState
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    func main() {
    	response, err := http.Get("http://www.baidu.com")
    	if err != nil {
    		fmt.Println("Get response err = ", err)
    		return
    	}
    
    	defer response.Body.Close() //内容在body里面
    
    	fmt.Println("response.status = ", response.Status)         //response.status =  200 OK
    	fmt.Println("response.StatusCode = ", response.StatusCode) //200
    	fmt.Println("response.Header = ", response.Header)
    	//fmt.Println("response.Body = ", response.Body) //response.Body =  &{[] 0xc000226080  }
    	buf := make([]byte, 4*1024)
    	var tmp string
    	for true {
    		BodySize, err := response.Body.Read(buf)
    		if BodySize == 0 {
    			fmt.Println("read err = ", err)
    			break
    		}
    		tmp += string(buf[:BodySize])
    	}
    
    	fmt.Println("tmp = ", tmp)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    单任务百度贴吧小爬虫

    package main
    
    import (
    	"fmt"
    	"net/http"
    	"os"
    	"strconv"
    )
    
    // https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=150
    
    // HttpGet 爬取网页内容
    func HttpGet(url string) (result string, err error) {
    	response, err1 := http.Get(url)
    	if err1 != nil {
    		err = err1
    		return
    	}
    
    	defer response.Body.Close()
    
    	//读取网页body
    	buf := make([]byte, 1024*4)
    
    	for true {
    		readSize, err := response.Body.Read(buf)
    		if readSize == 0 { //读取结束,或者出问题
    			fmt.Println("response body read err = ", err)
    			break
    		}
    		result += string(buf[:readSize])
    	}
    
    	return
    }
    
    func DoWork(start, end int) {
    	fmt.Printf("正在爬取%d到%d的页面\n", start, end)
    
    	//明确目标(要知道你准备在那个范围或者网站去搜索)
    	for i := start; i <= end; i++ {
    		//strconv.Itoa((i-1)*50)//整型转string
    		url := "https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
    		fmt.Println("url =", url)
    		//爬(将所有的网站的内容全部爬下来)
    		result, err := HttpGet(url)
    		if err != nil {
    			fmt.Println("HttpGet err = ", err)
    			continue
    		}
    
    		//把内容写入到文件
    		fileName := strconv.Itoa(1) + ".html"
    		file, err := os.Create(fileName)
    		if err != nil {
    			fmt.Println("create err = ", err)
    			continue
    		}
    
    		_, err1 := file.WriteString(result)
    		if err1 != nil {
    			fmt.Println("write string err = ", err)
    			continue
    		} //写内容
    		err2 := file.Close()
    		if err2 != nil {
    			fmt.Println("close err = ", err2)
    			continue
    		} //关闭文件
    	}
    }
    func main() {
    	var start, end int
    	fmt.Println("请输入起始页(>=1):")
    	fmt.Scan(&start)
    	fmt.Println("请输入终止页(>=起始页):")
    	fmt.Scan(&end)
    
    	DoWork(start, end)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    并发版网络爬虫

    package main
    
    import (
    	"fmt"
    	"net/http"
    	"os"
    	"strconv"
    )
    
    // https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=150
    
    // HttpGet 爬取网页内容
    func httpGet1(url string) (result string, err error) {
    	response, err1 := http.Get(url)
    	if err1 != nil {
    		err = err1
    		return
    	}
    
    	defer response.Body.Close()
    
    	//读取网页body
    	buf := make([]byte, 1024*4)
    
    	for true {
    		readSize, err := response.Body.Read(buf)
    		if readSize == 0 { //读取结束,或者出问题
    			fmt.Println("response body read err = ", err)
    			break
    		}
    		result += string(buf[:readSize])
    	}
    
    	return
    }
    
    // 爬取一个网页
    func SpiderPage(i int, page chan int) {
    	//strconv.Itoa((i-1)*50)//整型转string
    	url := "https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
    	fmt.Printf("真正爬%d页的网页:%s\n", i, url)
    	//爬(将所有的网站的内容全部爬下来)
    	result, err := httpGet1(url)
    	if err != nil {
    		fmt.Println("HttpGet err = ", err)
    		return
    	}
    
    	//把内容写入到文件
    	fileName := strconv.Itoa(i) + ".html"
    	file, err := os.Create(fileName)
    	if err != nil {
    		fmt.Println("create err = ", err)
    		return
    	}
    
    	_, err1 := file.WriteString(result)
    	if err1 != nil {
    		fmt.Println("write string err = ", err)
    		return
    	} //写内容
    	err2 := file.Close()
    	if err2 != nil {
    		fmt.Println("close err = ", err2)
    		return
    	} //关闭文件
    
    	page <- i //写i
    }
    func doWork1(start, end int) {
    	fmt.Printf("正在爬取%d到%d的页面\n", start, end)
    	page := make(chan int)
    	//明确目标(要知道你准备在那个范围或者网站去搜索)
    	for i := start; i <= end; i++ {
    		go SpiderPage(i, page)
    	}
    
    	for i := start; i <= end; i++ {
    		fmt.Printf("第%d个页面爬取完成\n", <-page)
    	}
    }
    func main() {
    	var start, end int
    	fmt.Println("请输入起始页(>=1):")
    	fmt.Scan(&start)
    	fmt.Println("请输入终止页(>=起始页):")
    	fmt.Scan(&end)
    
    	doWork1(start, end)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
  • 相关阅读:
    界面控件Telerik UI for WPF - 如何使用RadSpreadsheet记录或评论
    Qt ffmpeg音视频转换工具
    web前端-javascript-立即执行函数(说明、例子)
    pytorch-实现运动鞋品牌识别
    Linux安装nacos
    在Ubuntu/Linux中自动备份MySQL数据库
    Go 异常处理
    Prometheus监控实战之prometheus联邦集群
    idea免费插件分享
    MapUtil的使用
  • 原文地址:https://blog.csdn.net/qq_40432598/article/details/132787245