go与http协议

 http请求流程

在浏览器地址栏键入URL,按下回车之后会经历以下流程:

1、浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;

2、解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;

3、浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;

4、Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。

5、若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

6、客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。 

 报文格式

请求报文

请求头可以设置,比如爬虫时需要伪装成浏览器可以设置User-Agent :

client := &http.Client{}
	req,_ := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50")
	resp,_ := client.Do(req)

还可以设置Connection为Close时为短连接请求,默认不设置或设置为Keep-Alive时为长连接请求

响应报文 

响应头也可以设置,比如Content-Type默认返回text/html,浏览器看到这个返回值会将返回的body文本渲染成html页面,如果改成下面的值,浏览器不进行渲染,直接展示原文本。

func handlefunc(w http.ResponseWriter, r *http.Request)  {
	w.Header().Set("Content-Type", "text/plain")
}

http请求方法

GET     请求指定的页面信息,并返回实体主体。
HEAD    类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
POST    向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
PUT     从客户端向服务器传送的数据取代指定的文档的内容。
DELETE  请求服务器删除指定的页面。
CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS 允许客户端查看服务器的性能。
TRACE   回显服务器收到的请求,主要用于测试或诊断。

 跨域资源共享(CORS)

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器  让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求

设置返回报文 

跨域机制是一种浏览器的行为。当浏览器发起跨域请求时,默认情况下响应头里没有Access-Control-Allow-Origin这一项,此时服务器仍然会正常返回资源,但浏览器拦截返回并且报错,如果如下所示设置为“*”,允许跨域请求,则浏览器正常接收资源。这里的“*”代表来自所有域名的请求都允许跨域。可以指定允许某个域名,由于只允许设置一个值,所以可以通过r.Proto和r.Host获取协议ip地址和端口号,拼成完整的URL填入。只要不是访问相同域名,端口相同,协议相同,就是跨域 。

func handlefunc(w http.ResponseWriter, r *http.Request)  {
	w.Header().Set("Access-Control-Allow-Origin", "*")
}

如果请求方法不是get,head,post ,请求头中Content-Type的值不是text/plain、multipart/form-data、application/x-www-form-urlencoded或者请求头里设置了一些规定以外的值,即使设置了Access-Control-Allow-Origin,仍然不可以跨域,此时会增加一项额外的预检验请求,可以在返回头里面设置Access-Control-Allow-Headers,和Access-Control-Allow-Methods的值以允许跨域,如果设置了Access-Control-Max-Age的值例如1000,则在1000秒内不会发起预检验请求。

参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

jsonp(JSON with Padding)

客户端在调用js文件的时候又不受跨域影响,比如引入jquery框架的,或者是调用相片的时候,拥有scr这个属性的标签都可以跨域例如<script><img><iframe>。可以将数据以js文件的形式传递,客户端请求js文件,服务端以js文件的形式返回,服务端也可以根据请求动态生成js文件。

参考https://www.cnblogs.com/chenshishuo/p/4919224.html

代理服务器

设置一台同域内的服务器,将跨域的请求通过这台服务器转发。

最后,由于拦截跨域是浏览器的事,所以直接设置浏览器--disable-web-security就允许跨域了。

缓存头Cache-Control

Cache-Control 通用消息头字段,被用于在http 请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的, 这意味着在请求设置的指令,在响应中不一定包含相同的指令。

若缓存可用且请求的URL和之前缓存内容的一样,则不会向服务器发请求,直接读取缓存。

可缓存性:

public表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。

private表明响应只能被发送请求的客户端单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它),可以缓存响应内容。

no-cache每次都不直接使用缓存,需要先向服务器发起验证,验证通过后则直接读取缓存。

only-if-cached表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝

还有缓存到期,重新验证,重新加载等设置参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control

Last-Modified

位于返回头中,表示上次修改时间,下一次浏览器发起请求时可以将这个值,传给请求头中的If-Modified-SinceIf-Unmodified-Since发送给服务器,服务器中获取该值,通过比较现在的值告诉浏览器是否直接使用缓存。

语法:Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

Etag

位于返回头中,表示对于资源唯一的签名(例如一个哈希计算后的值),下一次浏览器发起请求时可以将这个值,传给请求头中的If-Match或If-Non-Match发送给服务器,服务器中获取该值,通过比较现在的值告诉浏览器是否直接使用缓存。

func handlefunc(w http.ResponseWriter, r *http.Request)  {
	if r.Header.Get("If-Match") == "595aee64-504"{
		w.WriteHeader(304)  //状态码304表示无需再次传输请求的内容,也就是说可以使用缓存的内容。
	}else {
		w.Header().Set("Catch-Control", "max-age=2000") //如果返回头的键没有首字母大写,go会自动将其规范化
                w.Header().Add("Catch-Control","no-cache")
		w.WriteHeader(200)  //写状态码必须在设置返回头之后
		io.WriteString(w,"hello world")  //写入的语句必须在写状态码之后
                or do other thing...
	}
}

cookie

Cookie 在请求头中,其中含有先前由服务器通过 Set-Cookie  返回头并存储到客户端。

这个首部可能会被完全移除,例如在浏览器的隐私设置里面设置为禁用cookie。

 语法: Cookie: name=value; name2=value2; name3=value3  ,有些属性名可以没有属性值

w.Header().Set("Set-Cookie", "id=1233")
w.Header().Add("Set-Cookie", "HttpOnly")

c,err := r.Cookie("id")  //打印c结果为id=1233。获取未设置的属性值会返回错误
c,err := r.Cookie("HttpOnly")  //打印c结果为HttpOnly=

Expires=<date> 和Max-Age=<non-zero-digit>设置过期时间,如果两个都设置了仅Max-Age生效。如果不设置过期时间,则浏览器关闭后cookie清空

设置HttpOnly属性,cookie 不能使用 JavaScript 经由  Document.cookie 属性、XMLHttpRequest 和  Request APIs 进行访问,以防范跨站脚本攻击(XSS)。

Domain=<domain-value>某个域名下设置的cookie,其他域名服务器不能访问到,但是可以设置域名下面的二级域名可以访问,例如设置了Domain=example.com,则浏览器访问a.example.com时也会自带cookie。

如果把用户的ID写到cookie里面,那这就可以称为用cookie的方式实现了session。考虑到安全隐私问题可以将ID经过某种计算再存到cookie里,服务器通过这个值再还原为ID。

connection

浏览器在请求头中发送这个属性,属性值为keep-alive时表示发起长连接,服务端可选是否建立长连接,返回keep-alive表示同意,返回close表示还是建立短连接。服务端可以设置keep-alive最大保持时间,超时断开。

如下图所示,只有当属性connection值为keep-alive时,属性keep-alive才有意义,timeout单位为秒,max表示在连接关闭之前,在此连接可以发送的请求的最大值。在非管道连接中,除了 0 以外,这个值是被忽略的,因为需要在紧跟着的响应中发送新一次的请求。HTTP 管道连接则可以用它来限制管道的使用。

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Thu, 11 Aug 2016 15:23:13 GMT
Keep-Alive: timeout=5, max=1000

当服务器返回的资源较多时,由于浏览器实现了多线程,会和服务器同时建立多个tcp连接加快加载资源,此时每个连接都有自己的Connection ID。如下图,当请求“/”时返回html页面,img标签又发起三次请求,请求地址为“/1.jpg” “/2.jpg” “/3.jpg”,这时返回图片

func handlefunc(w http.ResponseWriter, r *http.Request)  {
	if r.URL.Path == "/"{
		t,_ := template.ParseFiles("myjpg.html")
		t.Execute(w,nil)
	}else {
		f,_ := os.OpenFile("1.jpg", os.O_APPEND, 0666)
		cont,_ := ioutil.ReadAll(f)
		w.Write(cont)
	}
//myjpg.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
图片
<img src="1.jpg">
<img src="2.jpg">
<img src="3.jpg">
</body>
</html>

浏览器显示页面 

 浏览器中可见有3个Connection ID,请求到html文件后,又并发发起3次get请求获取图片,其中1.jpg由于默认采用了长连接所以延用了第一次的tcp连接,如果第一次返回头设置了w.Header().Set("Connection","close"),结果如下图。

数据协商 

客户端通过请求头中的某些属性向服务器协商想要获得什么样数据格式的返回。

Accept 请求头用来告知客户端可以处理的内容类型,服务器可以从诸多备选项中选择一项进行应用,并使用 Content-Type 应答头通知客户端它的选择。浏览器会基于请求的上下文来为这个请求头设置合适的值,比如获取一个CSS层叠样式表时值与获取图片、视频或脚本文件时的值是不同的。格式如下

精确指定类型
Accept: text/html
image/*可以用来指代 image/png, image/svg, image/gif 以及任何其他的图片类型
Accept: image/*
*/*表示任意类型,如果加数字如0.9,0.8是权重表示优先级
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8

 Accept-Encoding 会将客户端能够理解的内容编码方式(通常是某种压缩算法)进行通知

Accept-Encoding: gzip
Accept-Encoding: gzip, compress, br
Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1

Accept-Language请求头允许客户端声明它可以理解的自然语言,以及优先选择的区域方言。

Accept-Language: <language>
Accept-Language: <locale>
Accept-Language: *
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

Accept-Charset 请求头用来告知(服务器)客户端可以处理的字符集类型。

Accept-Charset: iso-8859-1
Accept-Charset: utf-8, iso-8859-1;q=0.5
Accept-Charset: utf-8, iso-8859-1;q=0.5, *;q=0.1

User-Agent 告诉服务器客户端的应用类型、操作系统(帮助判断是电脑还是手机)、软件开发商以及版本号。

User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0
  • Mozilla/5.0 是一个通用标记符号,用来表示与 Mozilla 兼容,这几乎是现代浏览器的标配。
  • platform 用来说明浏览器所运行的原生系统平台(例如 Windows、Mac、Linux 或 Android),以及是否运行在手机上。搭载 Firefox OS 的手机仅简单地使用了 "Mobile" 这个字符串;因为 web 本身就是平台。注意 platform 可能会包含多个使用 "; " 隔开的标记符号。参见下文获取更多的细节信息及示例。
  • rv:geckoversion 表示 Gecko 的发布版本号(例如  "17.0")。在近期发布的版本中,geckoversion 表示的值与 firefoxversion 相同。
  • Gecko/geckotrail 表示该浏览器基于 Gecko 渲染引擎。
  • 在桌面浏览器中, geckotrail  是固定的字符串 "20100101" 。
  • Firefox/firefoxversion 表示该浏览器是 Firefox,并且提供了版本号信息(例如  "17.0")。

Content-Type通知对方发送的内容的类型。浏览器会在某些情况下进行MIME查找,并不一定遵循此标题的值; 为了防止这种行为,可以将标题 X-Content-Type-Options 设置为 nosniff。客户端也可以发送Content-Type属性,可直接发送或在html页面中通过标签里的enctype属性发送。

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

 Content-Encoding通知对方采用了哪种压缩

Content-Encoding: gzip

 如下设置返回头并压缩为gzip格式发送

func handlefunc(w http.ResponseWriter, r *http.Request)  {
	if r.URL.Path == "/"{
		t,_ := template.ParseFiles("myjpg.html")
		t.Execute(w,nil)
	}else {
		gw := gzip.NewWriter(w)
		w.Header().Add("Content-Encoding","gzip")
		f,_ := os.Open("./1.jpg")
		defer f.Close()
		defer gw.Close()
		buf := make([]byte, 1024)
		for {
			c, err := f.Read(buf)
			gw.Write(buf[:c])
			if err == io.EOF {
				break
			}
		}
	}
}

可见图片经过压缩发送后,传送的文件已经变小,在浏览器端接收到后会还原成原文件

重定向

Location在返回头中指定的是需要将页面重新定向至的地址

func handlefunc(w http.ResponseWriter, r *http.Request)  {
	if r.URL.Path == "/"{
		w.Header().Add("Location","/new")
		w.WriteHeader(302)
	}
	if r.URL.Path == "/new" {
		w.Write([]byte("next page"))
	}
}

返回302状态码代表临时跳转,每次都会先访问原地址服务器。如果返回301表示永久跳转,浏览器会缓存这个对应信息,下次访问不经过服务器直接跳转访问下一个地址,直到浏览器清楚缓存。

内容安全策略(CSP)

用于检测和减轻用于 Web 站点的特定类型的攻击,例如 XSS 和数据注入等。

Content-Security-Policy可以限制资源获取及报告资源获取越权,一般放在返回头中或HTML文件的<meta>标签中,例如

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

 如下限制条件为禁止所有链接的访问,图片被拦截

if r.URL.Path == "/"{
		w.Header().Add("Content-Security-Policy", "default-src")
                ...

在default-src后面添加允许访问的范围,'self'表示允许对和html主文件相同域名的访问(此时图片可正常加载),添加*.baidu.com表示允许对baidu.com及其子域名的访问

w.Header().Add("Content-Security-Policy", "default-src 'self' *.baidu.com")

这段表示对本域名所有资源都允许访问,本域名外其他类型资源允许访问的范围

Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com ; script-src https://scripts.example.com 

当头属性是Content-Security-Policy-Report-Only时表示只报告不拦截。 如果Content-Security-Policy-Report-Only 头部和 Content-Security-Policy 同时出现在一个响应中,两个策略均有效。在Content-Security-Policy 头部中指定的策略有强制性 ,而Content-Security-Policy-Report-Only中的策略仅产生报告而不具有强制性。

支持CSP的浏览器将始终对于每个企图违反你所建立的策略都发送违规报告,如果策略里包含一个有效的report-uri 指令。

如下,超限制的访问会发送报告到指定地址:

 Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi

违例报告的语法

作为报告的JSON对象报告包含了以下数据:

document-uri发生违规的文档的URI。

referrer违规发生处的文档引用(地址)。

blocked-uri被CSP阻止的资源URI。如果被阻止的URI来自不同的源而非文档URI,那么被阻止的资源URI会被删减,仅保留协议,主机和端口号。

violated-directive违反的策略名称。

original-policy在 Content-Security-Policy HTTP 头部中指明的原始策略。

修改上面的头为如下,并添加handlefunc2即可打印json格式的报告

w.Header().Add("Content-Security-Policy", "default-src 'self'; report-uri http://127.0.0.1:8080/report ")
func handlefunc2(w http.ResponseWriter, r *http.Request)  {
		s,_ := ioutil.ReadAll(r.Body)
		fmt.Println(string(s))
}
func main()  {
	http.HandleFunc("/",handlefunc)
	http.HandleFunc("/report",handlefunc2)
	http.ListenAndServe(":8080",nil)
}

使用Content-Security-Policy-Report-Only也会打印报告

猜你喜欢

转载自blog.csdn.net/sinat_26682309/article/details/89609704