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

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

#

go语言基础---8_第1张图片

HTTP编程

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

func ListenAndServe(addr string, handler Handler) error

ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。
go语言基础---8_第2张图片

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服务器获取客户端的一些信息

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
}
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客户端编程

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

单任务百度贴吧小爬虫

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

并发版网络爬虫

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

你可能感兴趣的:(golang,iphone,开发语言)