go语言基础---8

Http请求报文格式分析

go 复制代码
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]))

}
go 复制代码
#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

#

HTTP编程

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

go 复制代码
func ListenAndServe(addr string, handler Handler) error

ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。

go 复制代码
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)
}

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

go 复制代码
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
}
go 复制代码
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)
}

http客户端编程

go 复制代码
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
}
go 复制代码
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 <nil> <nil>}
	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)
}

单任务百度贴吧小爬虫

go 复制代码
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)
}

并发版网络爬虫

go 复制代码
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)
}
相关推荐
Swift社区2 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht2 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht2 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20242 小时前
Swift 数组
开发语言
stm 学习ing3 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc4 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe4 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin4 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python