HTTP协议基础(HTTP 1.1)

来源:互联网 发布:ubuntu开机显示grub 编辑:程序博客网 时间:2024/04/29 11:35

http协议(目前主要是HTTP 1.1)是目前世界上使用最多的应用层协议,是互联网的基础,同时它也是程序开发中最基础的协议,所有程序员都应该知道http协议。这里根据本人目前的工作学习情况,参考一些规范,简单总结一下HTTP协议的一些基础知识。
主要参考规范:
rfc2616 - Hypertext Transfer Protocol – HTTP/1.1
rfc3986 - Uniform Resource Identifier (URI): Generic Syntax
rfc7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
rfc7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
下面的东西,如有问题,烦请指出,谢谢!

一、http特点
1、客户端/服务端模式
http协议是典型的服务端客户端模式,一切http请求都是由客户端发起,服务端只能被动接受。浏览器是很大的一类客户端,这种一般也叫作b/s,可以说http既能支持传统的c/s,也支持b/s。不过因为服务端不能主动发起通讯请求,所以http在开发即时应用时有先天不足,html5的websocket和新出的http2.0中的server push功能是对这一重大缺陷的改进。
2、无状态
http本身对事务无记忆能力,本身不保存通讯上下文,也就是这一次通讯不知道前一次通讯的结果,几次通讯之间本身可以不干扰。这样做的好处是服务器不用管之前的通讯,可以应答地很快,同时能够很简单的扩展服务端,缺点就是单独靠http无法完成复杂的逻辑。
要解决http本身不维持状态的问题,需要依靠自定义的协议,这种协议一般很简单,主要是在http报文中加一个唯一标识,同时依赖这个唯一标识保存一些会话信息。这个标识叫法有很多,比如sessionId、token等等,它们的基本作用是差不多的,就是标识请求方的综合身份(这个综合身份需要具体业务解释);具体的会话信息理论上双方都可以保存,但是现在为了安全,一般都由服务端保存。
3、弱连接(连接可主动断开)
http1.1之前,每次通讯完成后都有断开连接,这样影响了传输效率。http1.1引入Connection: keep-alive,并作为默认属性,使得建立一次tcp可以进行多次http通讯。虽然有keep-alive,但是仍然不能认为http连接是长期可靠的,客户端服务端都可以主动断开连接的。通常的做法是客户端在最后一次请求时携带Connection: close主动要求断开,服务端是一段时间无请求就主动断开。一般的基于原生tcp的应用都会想法设法维持连接,绝大多数时候连接断开属于异常行为,而http协议在通讯完成时断开连接是合理也是很正常的。
4、灵活简单
http对具体body报文格式如何解释并不作规定,可以方便地传输各种格式的报文,http本身对tcp进行了很好的封装,同时它的无状态、弱连接的特性,让大家在使用http协议通讯时基本不用关心tcp层的情况,开发者无需了解很多网络编程的知识即可快速有效地使用http。

二、Http Request
1、Http Request 基本格式
这里写图片描述
上图中标红的符号说明:
SP:代表白空格,也就是ASCII的0x20
CRLF:代表\r\n,也就是回车换行,\r是ASCII中的0x0d,\n是ASCII中的0x0a
COLON:代表英文冒号,也就是ASCII中的0x3a

2、Http Method
基于rfc2616的HTTP1.1规定了8个方法,分别是:

  • OPTIONS:用于查询服务器或指定资源的选项信息的请求,比如支持的方法以及一些特殊的配置,实际中本人仅仅是在跨域POST上传文件时碰到过这种请求,关于这个具体可以查看这两篇博文,讲了跨域与OPTIONS请求:
    http://www.ruanyifeng.com/blog/2016/04/cors.html
    http://stylechen.com/options-cors.html?utm_source=tuicool&utm_medium=referral

  • GET:rfc2616建议get做成幂等的、只读安全的、用于获取服务器数据的方法,实际情况中GET主要被用在获取网页静态数据、查询类接口、jsonp请求中,算是一个万能请求。

  • POST:用来向服务器提交数据,这个定义很宽泛,给了服务端很大的主动性,因此POST实际中也是一个万能的请求。最常见的是使用POST进行提交数据服务器写操作,一些webapi设计上也有使用POST提交json/xml等数据进行查询功能的,这与设计有关。

    本人的上一篇博文说了一些GET/POST之间的容易被误传的区别,这里说下实际中的一些区别,在某些功能的设计上需要考虑这些区别:
    a、GET是大多数浏览器默认请求;
    b、GET所需要传递的全部信息都在URL上,这意味着你可以手敲GET,还可以把URL发给别人,这比POST是个很大的优势(不带body的POST就不要用了);
    c、GET只识uri规范支持的字符,传递其他字符需要转码,常见的有URLEncoder和用于URL的Base64编码;
    d、浏览器基本上都支持GET缓存但是基本都不缓存POST,这意味着GET可以被存为书签,可以真正地有历史记录;
    e、jsonp只支持GET;
    f、GET很好被爬虫,能够被主流的搜索引擎收录,这对很多网站很重要。

    上面的不全是技术原因,很多时候是出于实用性和商业的考虑,实际中我建议是,如果在技术、安全、程序设计方便等指标上GET/POST无区别,还是用GET好;整体功能逻辑上的写操作都使用POST操作,有跨域时优先使用服务端的CORS相关设置,这样服务端有主动性,更好进行有效的安全控制。

  • HEAD:HEAD方法和GET方法的行为一致,但是HEAD方法能返回body部分,也就是只返回状态行和响应头的GET方法。

  • PUT:我个人对rfc2616中PUT的理解是它相当于数据库中的INSERT,具体更像REPLACE,不过服务端谁会让你随便创建数据/资源,因此PUT是不太常见的,很多情况下都被POST替代了。

  • DELETE:相当于数据库的DELETE,但是和PUT一样,被服务端限制了,基本是被POST替代了。

  • TRACE:用于http回环测试,也就是客户端发送什么,服务端就回应什么。TRACE方法让客户端知道了服务端接收到的具体数据,原本是用来诊断调试http问题的,但是很容易被黑客用来发现服务器的问题从而恶意利用或者发起攻击,现在的服务器一般都禁止TRACE请求,或者重定向为GET等请求。

  • CONNECT:HTTP 1.1新增的用于连接HTTP代理的方法,正常情况比较少碰到CONNECT方法。

关于http方法的其他:
a、使用过fiddler的朋友可能会发现,fiddler模拟请求时的http方法不止上面八种,这些多出来的方法是WebDAV(Web-based Distributed Authoring and Versioning,一种基于http1.1的扩展协议)中定义的新方法,并不http规范中原生的方法,也不是所有服务器浏览器都支持的。实际上http是基于tcp的文本协议,很容易自定义方法扩展http协议,这样做基本上也不用更改整个的http协议解析过程。
这里写图片描述
b、最常见的http方法是get和post,不过最近随着restful的webapi越来越被看重,PUT/DELETE这两种方法也逐渐被正常支持并使用,这四种方法分别对应着webapi中的功能意义上的增(PUT)、删(DELETE)、改(POST)、查(GET)。
c、对于http方法,建议是如果没必要使用这个方法,就在http服务器上禁止这个方法,一个http资源或者接口提供的http方法应该在满足需求的情况下尽量少。
d、HTTP方法在很多实现中是区分大小写的,碰见错误的大小写,tomcat7会直接返回”501 Not Implemented”,vertx会识别为”OTHER”方法,全部大写才是规范方法名。这一点也不要觉得奇怪的,通常的http实现中把请求行一起提取出来是最直接的,请求行中uri只是主机域名不区分大小写,别的还是区分大小写的,一般的普通请求的请求行中是没有主机域名的。

3、Http URI
这里就不讨论URI和URL的区别了,这东西讨论起来还很麻烦,uri的定义在rfc3986中,网上问这个问题的很多,可以看下这个问题,或者baidu google搜下相关的也行。

先说下uri的格式,rfc7230的5.3节规定了四种格式:源路径格式(origin-form,或者叫服务器上的绝对路径格式)、绝对格式(absolute-form)、认证格式(authority-form,只用于CONNECT方法),星号格式(asterisk-form,只用于OPTIONS方法),这里说下前面两种。
比如请求http://192.168.4.222:12345/upload?arg=1,2
源路径格式的http请求发送的请求行是这个:GET /upload?arg=1,2 HTTP/1.1
相关属性如下(使用的是vertx web)
request.absoluteURI() = “http://192.168.4.222:12345/upload?arg=1,2”
request.uri() = “/upload?arg=1,2”
request.query() = “arg=1,2”
request.path() = “/upload”

绝对格式的URI的请求行是:GET http://192.168.4.222:12345/upload?arg=1,2 HTTP/1.1
相关属性如下(使用的是vertx web)
request.absoluteURI() = “http://192.168.4.222:12345http://192.168.4.222:12345/upload?arg=1,2”
request.uri() = “http://192.168.4.222:12345/upload?arg=1,2”
request.query() = “arg=1,2”
request.path() = “/upload”
可见这种绝对格式的uri对现在的一些http服务程序的某些api有影响。
目前大多数http客户端在发送普通http请求是都是使用的源路径格式的uri,现在请求行中使用绝对格式uri一般用于对http代理服务器发起请求。不过根据rfc7230的5.3节内容,说是要求http服务器在以后要支持绝对格式的uri。

这里再说几点常用的知识:
a、http uri没有定义最大长度,长度限制是客户端和服务端自己定的;
b、http源路径格式的uri 必须以”/”开头,对http://www.abcd.com发起http请求,实际请求的URL是http://www.abcd.com/,URI是”/”,补充这个”/”的操作是客户端自己完成的,详见rfc7230 5.3.1节;
c、uri只支持很少的一部分字符(见rfc3986 1.2.1节,不是ASCII字符集,因为白空格SP也是uri不支持的,需要转义成”%20”或者变成”+”),不支持的字符需要进行相应转换才是合法uri,常见转换方式有URLEncode和用于URL的Base64编码(编码后的字符串没有”+”和”/”的那种)。顺便说下,有些浏览器(chrome/firefox)的地址栏看着可以显示中文,但这只是视觉效果,你copy一份就知道了;
这里写图片描述
d、http uri传递数组,有人说用英文逗号分隔a=1,2,有人说用a=1&a=2这种形式,有人说a[]=1&a[]=2这种形式,实际上服务端最初接收到的都是Map(String -> String),具体能不能转化成数组还得看你使用的web框架支不支持。

4、HTTP version
目前最常见的是HTTP/1.1,其他的还有HTTP/0.9、HTTP/1.0、HTTP/1.2、HTTP/2.0,2.0是有重大改进的新版本。如果服务器不支持某个HTTP版本,一般会返回”501 Not Implemented”。
跟HTTP Method一样,Version字段在很多实现中也是区分大小写的,碰见错误的大小写,tomcat7会返回”505 HTTP Version Not Supported”,vertx返回”501 Not Implemented”。

5、HTTP Request Headers
HTTP请求头相当于HTTP方法的控制参数,主要是辅助http方法实现某些功能。http请求头是String: String的键值对,在HTTP1.1中key是不区分大小写的,HTTP2.0中是要求全部小写key,value部分除非有特殊规定(比如Content-Coding等规定字符集、编码的,具体的可以查看rfc2616),否则是大小写敏感。HTTP请求头除了几个rfc定义的几个,还可以由程序自己定义,所以这个东西很灵活。
http不限定headers的整体长度和数目,但是服务器默认实现一般都有限制,tomcat8的默认是maxHeaderCount=100,netty 的 HttpServerCodec默认是headerSize=4096,浏览器也可能会限制这些。
rfc中关于Headers的可以查看rfc2616的第14节、rfc7231的第5节和第7节,关于req和resp的都有。
列一下常见的HTTP Request Heaaders:
Accept:客户端能够接收的内容类型
Accept-Charset:客户端可以接受的字符集
Accept-Encoding:浏览器可以支持的web服务器返回内容压缩编码类型
Accept-Language:客户端可接受的语言
Connection:是否需要持久连接(HTTP 1.1默认此header为keep-alive)
Cookie:这个都知道,具体关于cookie可以自己再谷歌百度下,更多更详细,这个header还是很重要的
Content-Length:request的body部分的字节长度
Content-Type:body部分对应的MIME信息
Host:指定请求的服务器的域名和端口号
Referer:先前网页的地址,即从你是从哪里访问到当前的静态资源/接口,简单点就是你从哪里点进来的
User-Agent:User-Agent的内容包含发出请求的用户信息
Transfer-Encoding:有两个可选值,默认是identity,这个也是不设置此Header时的http默认值;trunked,代表使用块传输编码,此值会无效Content-Length
Origin:cors跨域指明发起请求的js的来源
Access-Control-Request-xxxx:用于跨域请求,询问服务器相应的http请求属性能够使用哪些值,具体可以看下点上面两个关于跨域的博文链接进去看下。

常见的HTTP Response Heaaders:
Allow:对某网络资源的有效的请求行为,不允许则返回405
Content-Encoding:对应req的Accept-Encoding
Content-Language:对应req的 Accept-Language
Content-Type:body部分对应的MIME信息
Content-Length:resp body的长度,规则同req body,受Transfer-Encoding影响
Cache-Control:客户端缓存控制,缓存在客户端,使用chrome查看返回码是200,但是会标注”from disk cache/from cache”,这类缓存有效是不会真的向服务器发起请求的,详见rfc2616 14.9节
Expires:资源过期时间,也是客户端缓存控制,优先级比Cache-Controll低
If-xxxx:这结果比较的,主要是判断资源是否修改,如果没有修改,服务器返回304 Not Modified但是不发送body部分有修改则返回最新的资源。这是是一种服务端缓存机制,可以让服务器不发送body部分,可以减轻网络负载,客户端收到304后是从自己的本地缓存中取出body部分
E-Tag:一般同上面的If-xxxx配合使用,这两个可以参考rfc2616 第14节
Location:返回码301/302时的重定向url
Pragma:也是经常被用于缓存从中,比如Pragma: no-cache
Set-Cookie:设置cookie
Refresh:告诉浏览器多久后刷新一次,可以指定刷新重定向url,常用于http错误页面,这个并不是规范的header,不过大多数浏览器都支持

Response Header 与 HTML <meta http-equiv=”xxx”> 的关系:
在早期很多http服务器实现得很简单,有些服务器不支持设置header,这就造成了问题。一个明显的就是服务器无法设置header中的Content-Type,也无法对静态html页面动态使用Content-Type,很容易出现编码错误。为了解决这种情况,就让html自带一些信息,这些信息充当http resp headers,于是就出现了<meta http-equiv=”xxx”>这个标记。<meta http-equiv=”xxx”>的优先级是低于resp headers的,它是为了在无headers的情况下充当headers,有了正宗的headers它肯定要让路。
参考:http://reference.sitepoint.com/html/meta/http-equiv

有些Header能够控制body部分,那些放在body部分说。

6、HTTP Request body
http req body和resp body的各种规则基本一样,这里放在一起说。
http req/resp body可以是任何类型的数据,不过它一般与req/resp Header中的Content-Type/Content-Length/Content-Encoding配合使用,许多web后台框架/http客户端会使用这三项的值来解析req/resp body中的数据。
GET请求是可以包含req body的,body部分的长度可以是无限的(http服务程序配置更改),这两个本人前一篇博文已经说了,其他的关于方法与body的关系可以查看rfc2616和rfc7231.

当http服务器收到静态的资源文件请求时,依据该静态资源文件后缀名在mime配置文件中找到对应的<extName, mimeType>对,把其中的mimeType设置给resp的Content-Type,客户端再根据resp的Content-Type的处理静态文件。通常你可以把Content-Type和mimeType理解成一个东西。
下面列出一些常见的静态文件Content-Type(mimeType):
text/plain:纯文本,在一些浏览器中text/plain,也被用来兼容json或者xml
text/css:css文件
text/html:html文件
application/javascript:js文件
application/json:标准的json
application/xml:标准的xml
上面几项文本格式的Content-Type可以同时指定字符编码,比如text/html; charset=utf-8
image/gif、image/jpeg、image/png:常见的图片
application/octet-stream:二进制流
application/msword:doc文件
更多的可以参考tomcat的web.xml或者nginx的mime配置文件。

再说两个比较重要的关于req的Content-Type:
application/x-www-form-urlencoded:form表单默认的Content-Type,数据被编码为键值对,用”&”分隔,跟URL传参的格式一样;
multipart/form-data:最常见的web http文件上传的Content-Type值。
这两个重要是因为许多web框架碰到这两个才会自动解析表单数据,不设置又没有手动解析的话就相当于没有post表单数据。
对于服务端调用http,Content-Type不是那么重要,服务端的设计一般都知道如何解析body数据。

Content-Length、Transfer-Encoding与body的关系:
如果没有设置Transfer-Encoding: chunked,那么就Content-Length的值就是body的长度,这需要在http中一次性发送这个body的所有内容。
如果设置了Transfer-Encoding: chunked,那么代表使用块传输编码,也就是body部分可以分块传输,不用一次性发送这个body的所有内容,对方会按照chunked格式分批读取数据并进行相应处理,此项在传输大内容时能够减轻服务端/客户端的压力。使用块传输编码,默认会告诉对方,body传输长度未知,这时Content-Length是会被无视的。
块传输编码现在常用于服务端的response,客户端的req很少使用这个。因为客户端在http通讯中占有主动性,可以很轻松的发起多次http请求来进行分块,浏览器也支持各种插件来实现功能丰富的大文件的上传;对于一次http请求,服务端是无法多次有效地响应的,必须得在一次http resp中响应完成,要不就会丢失状态信息,响应失败。
如果一个req/resp没有设置Transfer-Encoding: chunked,而且也没有指定Content-Length,或者Content-Length的值并不是body的长度,那么这个req/resp通常会被认为是无效的、错误的,服务器一般是返回“400 Bad Request”。正因为如此,常见的http服务程序和客户程序都会在发起req/resp前主动计算并设置Content-Length这个值,或者使用chunked编码传输,而不是由人为手动指定。
Transfer-Encoding的缺省值是identity,也就是默认情况是使用Content-Length。
更多的关于Transfer-Encoding: chunked的知识可以查看rfc2616的3.6节等。

Content-Encoding:body的内容编码,一般是压缩编码而非字符编码,字符编码在Content-Type中指定,默认是identity但是不被允许设置为此值(对应的Accept-Encoding则可以设置此值),这项配合Transfer-Encoding: chunked更好,能较好的实现流式压缩并发送,不用等全部数据都完成了再压缩、发送。

三、HTTP Response
1、Http Response 基本格式
这里写图片描述

2、Response Http Version
此项具体返回什么Http版本是由http服务程序实现的,状态码常见的有”501 Not Implemented”或者”505 HTTP Version Not Supported”。

3、Stats-Code和Description
这个大家应该很熟悉,它就是一个三位数字,description与stats-code配合使用用于描述状态码。详细的可以看rfc2616第10节和rfc7230第6节,这里列些常用的。
1xx:临时resp的状态码,此状态码的resp不能包含body。此状态是过渡状态,平时正常使用根本碰不到,编程中使用封装好的http客户端也基本不会遇到这个,但在一些tcp框架中,比如netty,其中的HttpObjectAggregator中就有1xx的处理,自己用netty处理http有时候也要考虑这种状态码。
2xx:表示请求最终成功的状态码,这个最常见,一般是200 OK 和 204 No Content,204是不能有resp body的,这个状态码告诉浏览器,请求成功了,但是没有内容给你,你可以不用刷新页面。
3xx:表示需要进一步操作,经常用于重定向,常见的301 Moved Permanently 永久改变导致重定向,302 Found 临时改变导致中定向,304 Not Modified 资源无修改请从你自己的本地缓存总取。301和302在重定向上功能是基本没区别的,在爬虫这种收录url的程序中有区别,碰见301一般爬虫会把url替换成重定向后新的url,碰见302则不会进行这种替换,因此302可以保留之前被搜索引擎收录的地址,所以实际中还是302更好更常见。
4xx:请求方有错误,常见的400 Bad Request 语法错误,403 Forbidden 请求被禁止(通常是权限问题导致的,svn中常见这个),404 Not Found 这个大家都知道,405 Method Not Allowed 请求中使用了错误的http method(此状态码要返回Allow这种header信息告诉客户端哪些方法是可行的)。
5xx:应答方有错误,常见的500 Internal Server Error 服务器处理请求时出现错误,501 服务器未实现某些方法(跟405有些区别,405是服务器中某些资源不支持请求方法,但是服务器本身能识别处理这种方法,其他的资源可以用这种方法;501是服务器本身就不识别处理请求方法,对所有资源使用这种方法都是501),502/504 nginx代理在碰到上游resp无效/resp超时时的返回,直接访问http服务器一般碰不到这两种状态码。

4、Response Header
见上面的Request Header

5、Response body
见上面的 Request Body

以上内容,如有问题,烦请指出,谢谢!

1 0
原创粉丝点击