网络协议——HTTP协议
目录
一、HTTP协议是什么
二、HTTP协议工作的过程
三、HTTP协议格式
(一)HTTP请求
(二)HTTP响应
四、HTTP请求格式
(一)认识URL
关于URL encode
(二)认识 "方法" (method)
使用Fiddler观察GET请求
使用Fiddler观察POST请求
GET和POST的区别
(三)认识请求报头(header)
Host
Content-Length
Content-Type
User-Agent(简称UA)
Referer
Cookie
(四)请求正文(body)
五、HTTP响应状态码
200 OK
404 Not Found
403 Forbidden
405 Method Not Allowed
500 Internal Server Error
504 Gateway Timeout
302 Move temporarily
301 Moved Permanently
状态码小结
六、通过Java Socket构造HTTP请求
一、HTTP协议是什么
HTTP全称为“超文本传输协议”,是一种非常广泛的应用层协议。
我们平时打开一个网站,就是通过HTTP协议来传输数据的。
当我们在浏览器中输入一个 搜狗搜索的 "网址" (URL) 时, 浏览器就给搜狗的服务器发送了一个 HTTP 请求, 搜狗的服务器返回了一个 HTTP 响应.
这个响应结果被浏览器解析之后, 就展示成我们看到的页面内容. (这个过程中浏览器可能会给服务器发送多个 HTTP 请求, 服务器会对应返回多个响应, 这些响应里就包含了页面 HTML, CSS, JavaScript, 图片, 字体等信息).
所谓 "超文本" 的含义, 就是传输的内容不仅仅是文本 (比如 html, css 这个就是文本), 还可以是一些其他的资源, 比如图片, 视频, 音频等二进制的数据.
二、HTTP协议工作的过程
当我们在浏览器中输入一个网址,此时浏览器就会对对应的服务器发送一个HTTP请求,服务器收到请求后,经过处理,就会返回一个HTTP响应。
事实上,当我们访问一个网站的时候,可能涉及不止一次的HTTP请求和响应。
可以通过抓包工具以Fiddler为例(下载地址: https://www.telerik.com/fiddler/)
查看这个交互过程:
- 左侧窗口显示了所有的 HTTP请求/响应,可以选中某个请求查看详情.
- 右侧上方显示了 HTTP 请求的报文内容.(切换到 Raw 标签页可以看到详细的数据格式)
- 右侧下方显示了 HTTP 响应的报文内容.(切换到 Raw 标签页可以看到详细的数据格式)
抓包工具的原理:
Fiddler相当于是一个“代理”。
浏览器访问sougou.com的时候,会把HTTP请求先发送给Fiddler,Fiddler再把请求发送给服务器,当服务器返回数据的时候,Fiddler拿到返回的数据,再把数据交给浏览器。
三、HTTP协议格式
(一)HTTP请求
- 首行:[方法] + [url] + [版本]
- Header:请求的属性,冒号分割的键值对;每组属性之间使用 \n 分隔;遇到空行表示 Header 部分结束
- Body:空行后面的内容都是 Body。Body 允许为空字符串。如果 Body 存在,则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度;
(二)HTTP响应
- 首行:[版本号] + [状态码] + [状态码解释]
- Header:请求的属性,冒号分割的键值对;每组属性之间使用 \n 分隔;遇到空行表示 Header 部分结束
- Body:空行后面的内容都是 Body。Body 允许为空字符串。如果 Body 存在,则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度;如果服务器返回了一个 html 页面,那么 html 页面内容就是在 body 中。
为什么HTTP报文中要存在“空行”?
1.因为HTTP协议中并没有规定报头部分的键值对有多少个,空行就是报头部分结束的标记,或者是用来分割报头与正文。
2.HTTP协议是依赖于TCP协议,TCP协议是面向字节流的,如果没有这个空行,就会出现“粘包问题”。
四、HTTP请求格式
(一)认识URL
URL是描述网络上唯一资源的位置的。
例如在jdbc:mysql://127.0.0.1:3306/java113?characterEncoding=utf8&useSSL=false#ch1中:
URL的部分 | 含义 |
jdbc:mysql:// | 协议名称 |
127.0.0.1 | 要访问的服务器域名或者ip地址 |
:3306 | 端口号,区分服务器上的哪个应用程序 |
/java113 | 带有层次结构的路径,想要访问的是某个主机上某个程序的某个资源 |
?characterEncoding=utf8&useSSL=false | 用?与路径隔开,标识查询字符串Query String,对要访问的资源补充说明,是键值对结构,键与值之间用=分割,键值对之间用&分割 |
#ch1 | 片段标识符,常见于文档类网页 |
URL 中可省略部分:
- 协议名:可省略,省略后默认 `http://`
- ip 地址/域名:HTML 里(如 `img`、`link` 等标签 `src`/`href` 属性)可省略,省略后默认与当前 HTML 所属 ip/域名一致
- 端口号:可省略,`http` 协议默认 80 端口,`https` 协议默认 443 端口
- 带层次的文件路径:可省略,省略后相当于 `/`,部分服务器遇 `/` 会自动访问 `/index.html`
- 查询字符串:可省略
- 片段标识:可省略
关于URL encode
像 / ?:等这样的字符,已经被url当做特殊意义理解了,因此这些字符在url中不能随意出现,比如某个参数中需要或者可能带有这类特殊字符,就必须对特殊字符进行转义处理。
URl encode的转义规则是:将需要转义的字符转为16进制,然后从右到左,取4位,每两位做一位,前面加上%,编码成%XY的格式。
例如:
当我们在浏览器中搜索C++时,+就被转义成了%2B
分享一个URL encode工具:UrlEncode编码/UrlDecode解码 - 站长工具
(二)认识 "方法" (method)
方法 | 说明 |
GET | 获取资源 |
POST | 传输实体主体 |
PUT | 传输文件 |
DELETE | 删除文件 |
HEAD | 获取报文首部 |
POTIONS | 询问支持的方法 |
TRACE | 追踪路径 |
CONNECT | 要求用隧道协议连接代理 |
LINK | 建立和资源之间的联系 |
UNLINK | 断开连接关系 |
在这些方法中,GET和POST方法是最常见的请求,从语义上来说
- GET是获取浏览器上的某个资源
- POST多用于提交用户输入的数据给服务器(例如登陆页面)
但是实际开发的时候,不一定严格按照语义来进行区分,GET也可以用于提交数据,POST也可以用于获取数据。
使用Fiddler观察GET请求
打开Fiddler,访问搜狗主页,观察抓包结果。
使用Fiddler观察POST请求
打开Fiddler,在gitee登录界面登录,就可以看到POST请求。
GET和POST的区别
- 语义不同: GET 一般用于获取数据, POST 一般用于提交数据.
- GET 的 body 一般为空, 需要传递的数据通过 query string 传递, POST 的 query string 一般为空, 需要传递的数据通过 body 传递
- GET 请求一般是幂等的, POST 请求一般是不幂等的. (如果多次请求得到的结果一样, 就视为请求是幂等的).
- GET 可以被缓存, POST 不能被缓存. (这一点也是承接幂等性)
至于PUT和DELETE方法,也使用于Restful api设计。
POST | 增 |
DELETE | 删 |
GET | 查 |
PUT | 改 |
(三)认识请求报头(header)
header的格式是键值对结构,每个键值对占一行,键与值之间使用分号加空格分割。
介绍常用的报头
Host
表示服务器主机的IP地址(或者域名)和端口号。
大部分情况下,host中的地址与url中的地址是一样的,如果使用了代理,可以通过host来获取到最原始的目标是什么。
Content-Length
表示body中的数据长度,单位是字节。
HTTP协议,就是把字符串构建成HTTP约定好的格式,基于TCP实现的。
TCP将这些字符串写入到TCP Socket中,对于一个TCP来说,一个连接上可以发送多个请求,服务器在收到这些数据的时候就要区分一下,从哪里到哪里是一个完整的请求。
- 如果是没有body的http数据,读到空行就可以认为结束了。
- 如果是有body的http数据,先解析header中Content-length的值,读到空行后,再读取固定字节的长度。
Content-Type
表示请求的body中的数据格式,提示了接收方如何解析body中的数据。
数据类型 | 格式 |
HTML | text/html |
CSS | text/css |
JS | application/javascript |
JSON | application/json |
图片 | image/png image/jpg |
User-Agent(简称UA)
里面表示了用户使用的设备的浏览器和操作系统的情况。
例如:
作用是通过UA中的浏览器版本/操作系统版本,区分出当前用户的设备,最多都支持哪些特性从而返回不同版本的页面。
Referer
描述了当前页面的来源,表示当前这个页面是从哪个页面跳转过来的。
搜索页跳转到广告页,广告页的referer:
Cookie
Cookie是浏览器允许网页在本地磁盘存储数据的一种机制。
网页不能直接操作设备上的其他数据,但可以通过浏览器,在其允许的范围内创建、读取、修改Cookie(这些Cookie仅由浏览器管理,且通常与特定网站绑定)。
简单说,当你访问网站时,网站通过浏览器在你的设备上留下Cookie(小型文本文件),这些文件就存放在浏览器的指定存储位置,属于本地设备上的数据,而非网站服务器上的数据。浏览器会在你后续访问该网站时,自动携带这些Cookie信息,方便网站识别你的状态和偏好。
Cookie是按照键值对的方式进行存储数据的,里面的数据都是程序员自定义的。
Cookie有一个典型的应用场景:登录和用户认证。
下面是登录的流程:
(四)请求正文(body)
body中的数据格式与报头中Conent-Type密切相关。
五、HTTP响应状态码
200 OK
这是最常见的状态码,表示访问网页成功。
404 Not Found
表示访问的资源没有找到。
当我们输入一个URL进行访问,URL中的ip定位到服务器主机,port定位到程序,path定位到程序管理的资源,出现404表示访问的资源在服务器上没有。
403 Forbidden
表示访问被拒绝,有的页面通常需要用户具有一定的权限才能访问(登陆后才能访问),如果用户没有登陆就直接访问,就容易见到403.
405 Method Not Allowed
表示请求的方法和服务器这边声明的注解不匹配,就会出现405。
500 Internal Server Error
表示服务器内部出现错误,处理逻辑的代码中抛出异常,但是没有catch到。
504 Gateway Timeout
当服务器负载较大的时候,服务器处理单条请求时消耗的时间就会变长,就可能导致超时的情况。
302 Move temporarily
表示临时重定向,在登陆页面中经常会见到 302,用于实现登陆成功后自动跳转到主页。
响应报文的 header 部分会包含一个 Location字段,表示要跳转到哪个页面。
301 Moved Permanently
永久重定向. 当浏览器收到这种响应时,后续的请求都会被自动改成新的地址。
状态码小结
状态码 | 类别 | 原因 |
1XX | 信息性状态码 | 接收的请求正在处理 |
2XX | 成功状态码 | 请求正常处理完毕 |
3XX | 重定向状态码 | 需要进行附加操作以完成请求 |
4XX | 客户端错误状态码 | 服务器无法处理请求 |
5XX | 服务器错误状态码 | 服务器处理请求出错 |
六、通过Java Socket构造HTTP请求
所谓的 "发送 HTTP 请求",本质上就是按照 HTTP 的格式往 TCP Socket 中写入一个字符串。
所谓的 "接受 HTTP 响应", 本质上就是从 TCP Socket 中读取一个字符串, 再按照 HTTP 的格式来解析。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;public class HttpClient {private Socket socket;private String ip;private int port;public HttpClient(String ip,int port) throws IOException {this.ip=ip;this.port=port;this.socket=new Socket(ip,port);}
//get报头public String get(String url)throws IOException{StringBuilder request=new StringBuilder();//构造首行request.append("GET "+url+" HTTP/1.1\n");//构造headerrequest.append("host: "+ip+':'+port+'\n');//构造空行request.append('\n');//发送请求OutputStream outputStream=socket.getOutputStream();//将request转为字节数组outputStream.write(request.toString().getBytes());//接收响应InputStream inputStream=socket.getInputStream();byte[]buffer=new byte[1024*1024];int n=inputStream.read(buffer);return new String(buffer,0,n,"utf-8");}
//post报头public String post(String url,String body)throws IOException{StringBuilder request=new StringBuilder();//构造首行request.append("POST "+url+" HTTP/1.1\n");//构造headerrequest.append("host: "+ip+':'+port+'\n');request.append("Content-Length: "+body.getBytes().length+'\n');request.append("Content-Type: text/plain"+'\n');//构造空行request.append('\n');//构造bodyrequest.append(body);//发送请求OutputStream outputStream=socket.getOutputStream();//将request转为字节数组outputStream.write(request.toString().getBytes());//接收响应InputStream inputStream=socket.getInputStream();byte[]buffer=new byte[1024*1024];int n=inputStream.read(buffer);return new String(buffer,0,n,"utf-8");}public static void main(String[] args) throws IOException {HttpClient httpClient = new HttpClient("www.baidu.com", 80);String resp = httpClient.get("/");System.out.println(resp);}
}
构造方法 HttpClient(String ip, int port) 负责连接服务器(主机和端口),而 get(String url) 方法的参数仅负责指定资源路径,这样拆分更符合 HTTP 协议的逻辑。
运行结果: