HTTP协议详解(一):工作原理、请求方法与状态码

为什么要学习 HTTP

HTTP 有一个特点:一次配置,终身使用!

在你接手一个老项目时,你可能不需要很了解 HTTP,你只要仿照之前的代码进行扩展即可,比如新增一个 API 接口。可是,别人一问你 HTTP 相关的知识就懵了。

另外,我们都知道 HTTPS 是安全的,但为什么安全,我们却不知道。一旦超出认知范围,我们就无法确保它的安全。又比如,你可能听过 TCP 的长连接和连接,但"不连接"又是指什么呢?还有,HTTP 传输数据的格式又有哪些?

再举个例子,同一个网络请求可能会有多种写法,在实际应用中,我该使用哪种?

总而言之,对大多数人来说,HTTP 的基础很重要。

HTTP 是什么

对于 HTTP 最直观的印象就是在浏览器地址栏输入地址,然后回车能够打开一个网页。作为安卓工程师,我们会这么认为:通过代码发送网络请求,然后获取响应结果。

HTTP 的官方定义是超文本传输协议(HyperText Transfer Protocol)。

其中 Protocol 在计算机科学中被称为通信协议。HyperText 是超文本,但 Hyper 不是超级的意思,而是扩展的意思。超文本指的是在电脑中显示的、含有可以指向其他文本的链接的文本,其实就是 HTML,用来传输 HTML 文本就是 HTTP 最初的用处。

HTTP 的工作方式

浏览器地址栏输入地址回车,浏览器发起请求到服务器,服务器拿到请求经过处理,响应对应的内容给浏览器,浏览器拿到响应,再将其通过渲染引擎(浏览器内核)渲染成可见的页面。

面对上述过程,我们可能会问:

  1. 地址是怎么转换成请求的?

  2. 请求和响应是什么样的?

首先,请求和响应大概是这样的。

请求:

H 复制代码
GET / HTTP/1.1
Host: example.com

响应:

H 复制代码
HTTP/1.1 200 OK
Server: nginx/1.13.3
Date: Sun, 01 Jul 2021 7:54:55 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Cache-Control: public, max-age=0
ETag: W/"4558-VULd89nkFI4Z8A/TkQk+Strh3nc
Vary: Accept-Encoding
Content-Encoding: gzip

<!DOCTYPE html>
<html>
<head>
...

我们再来看看地址(URL)是怎么转换为请求(报文)的。首先这是一个示例地址:

example.com/users?id=1

它会被浏览器拆成三部分:

  • http://: 协议类型

  • example.com: 服务器地址

  • /users?id=1: 路径(path)以及查询参数

拆解后,会解析成这样:

H 复制代码
GET /users?id=1 HTTP/1.1
Host: example.com

这就是 HTTP 的报文。

再来以安卓开发的角度看看 HTTP 的工作方式,其实和浏览器是一样的。在安卓中,我们通过代码向服务器发送请求,服务器返回响应,我们可以在代码中拿到服务器返回的数据,比如 JSON 格式的数据。但这不是服务器返回的响应报文,而是从响应报文的消息体中取出的一部分数据,

请求报文和响应报文

请求报文格式:Request

那报文是什么样的呢?请求报文大概长这样:

H 复制代码
GET /users HTTP/1.1 // 请求行
Host: example.com // 首部

第一行是请求行,其中 GET 是请求方法(method),/users 是访问的资源路径(path),用于给服务器看的。而下面的 example.com 是给接收到请求的主机看的。HTTP/1.1 是 HTTP 的版本号,主要有几个大版本号,0.9、1.0、1.1、2.0,现在 1.1 是最广泛应用的,0.9、1.0 已被废弃,2.0 也已被广泛应用。

第二行是首部(Header),它是键值对的形式,冒号左边是 key,右边是 value。另外 Header 可以是多行,例如:

H 复制代码
Host: example.com
Content-Type: text/plain
Content-Length: 213

请求报文还可以有 Body,它是报文内部的实质内容,也就是发送给服务器看的具体信息,比如新增用户的请求报文中,Body 部分可以是用户对象序列化后的字符串。

注意:Body 不是必须的,只是请求内容的话,可以不需要。比如使用 GET 请求获取 id 为 1 的用户信息,其请求报文可能为:

H 复制代码
GET /users/1 HTTP/1.1
...
// 并没有 Body 部分

为什么不将 id 放在 body 中,因为不能这么写。规则:path 是用来定位资源的,body 中的数据是用于给服务器处理使用的。

响应报文格式:Response

它的格式和请求报文很类似,也有 header 和 body。

H 复制代码
HTTP/1.1 200 OK // 状态行
content-type: application/json; charset=utf-8
catch-control: public, max-age=60, s-maxage=60
vary: Accept,Accept-Encoding
etag: W/"02eec5b334b0e4c05253d3f4138daa46"
content-encoding: gzip
{"err_no": 0,"err_msg": "success","data": {"id": "7534613534082072586","article_id": "0","user_id": "0","category_id": "0","tag_ids": [],"link_url": "","cover_image": "","is_gfw": 0,"title": "This is title","brief_content": "","is_english": 0,"is_original": 1,"edit_type": 10,"html_content": "deprecated","mark_content": "your_mark_content","ctime": "-62135596800","mtime": "-62135596800","status": 0,"original_type": 0,"theme_ids": []}}

第一行是状态行,其中 200 是状态码,OK 是状态信息,这两个是对这次响应的简单描述。状态码 200 表示请求成功,OK 作为补充的描述。

请求方法

请求方法最重要也最常用的有四个: GETPOST、PUT、DELETE。

GET

GET 用于获取资源,比如在浏览器地址栏输入 URL 并回车,此时的请求方法绝对是 GET。

GET 也是 HTTP 最初版本中的唯一方法,因为那时只需从服务器中获取数据。另外,它不对服务器数据进行操作(没有提供信息给服务器进行处理),所以请求报文中绝对没有 Body。

如果某个 Web API 的请求中含有 Body,那么它就是不规范的。而这种不规范可能会导致程序崩溃,比如你使用 Retrofit 去请求这样的接口,程序会报错:Method GET must not have a request body.

POST

POST 用于增加或修改资源,比如新增或修改一个用户。它是一定带有 Body 的,因为要通过 Body 提供信息给服务器去处理。

新增一个用户的请求报文大概长这样:

H 复制代码
POST /users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 19

name=martin&age=21  // <= Body

前面我们提到:同一个请求可能会有多种写法。POST 请求就是一个很好的例子,其 Body 数据的格式有好几种。这和 Content-Type 这个请求头有关:

  • application/x-www-form-urlencoded: 表单类型,就比如 name=martin&age=21。在 Retrofit 中对应着 @FormUrlEncoded@Field

  • application/json: 最常使用的,我们可以使用它来传递复杂的数据。在 Retrofit 中对应 @Body

  • multipart/form-data: 用于上传文件,比如上传图片。在 Retrofit 中对应 @Multipart@Part

知道了这些,你也就知道为什么同一个请求会有多种写法,并且知道该用哪种:根据后端需要的格式进行选择。

PUT

PUT 用于修改资源,也具有 Body。它和 POST 都可用于修改资源,在实际中选哪个都行。

另外,PUT 和 POST 有个区别,PUT 是幂等的,而 POST 是不具有幂等性的。PUT 多次修改用户,结果都是一样的,用户的信息被修改了。而 POST 多次新增用户,结果不一样,并不是只新增了一个用户,而是新增了多个用户。

GET 也是幂等的,因为它并没有对数据进行修改。

修改用户的请求报文大概长这样:

H 复制代码
PUT /users/1 HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

name=jack

DELETE

DELETE 用于删除资源,也是没有 Body。因为删除资源只需定位到对应资源,然后删除即可。

删除一个用户的请求报文大概长这样:

H 复制代码
DELETE /users/1 HTTP/1.1
Host: example.com

DELETE 是幂等的,因为不管多少次,结果都是删除一个用户。

HEAD 是特殊的请求方法,它的使用和 GET 完全相同,只不过它的响应报文没有 Body。

它的用处不是为了获取资源,而是用于获取信息。比如,在真正下载之前,你可以把请求改为 HEAD,因为响应中没有 Body,你可以很快拿到响应。然后从 Header 中得到资源的信息,进一步判断该怎么进行下载。

状态码

状态码是用于对响应结果做出简单的类型化描述,如 200 表示获取成功、404 表示内容未找到。

状态码可被归为以下五类:

  • 1xx: 临时性消息。

    • 101 表示 HTTP 协议切换。用于判断一个服务器是否支持 HTTP 2.0 协议。浏览器会先发送一条 HTTP 1.1协议的请求,并加上 Upgrade: h2,如果服务器支持,响应的状态码会是 101,这样以后浏览器就可以发送 HTTP 2.0 协议的请求了;不支持的话,服务器会忽略这个请求头,响应的状态码是 200,这样浏览器就应该发送 HTTP 1.1 协议的请求。

    • 100 表示继续发送。有时客户端要发送的请求体(Body)会比较大,这时请求头要加上 100-continue,表示我的请求内容还没发完,服务器就会响应 100 的状态码,表示我知道了,请你继续发吧。

  • 2xx: 成功。

    • 200 表示请求成功。

    • 201 表示创建成功。

  • 3xx: 重定向。

    • 比如你输入 http 开头的网址,然后回车,会发现竟然跳转到了 https 开头的网址。这是因为浏览器请求了你最开始输入的网站,发现响应的状态码是 301(表示资源永久迁移了),这时,浏览器会再次请求响应头中 Location 所指向的网址。

    • 另外,302 表示临时迁移。

  • 4xx: 客户端(浏览器或是应用)错误,也就是请求有问题。

    • 如请求了不存在的资源,状态码会是 404

    • 请求了未授权的资源,状态码会是 401

  • 5xx: 服务器错误。

    • 如服务器宕机了、服务器连接数据库失败,返回的响应码可能是 500502

其中最重要的就是 2xx 和 4xx,你只需记住状态码的类型,而不应该去记某一个具体的状态码。

相关推荐
卑微的小鬼29 分钟前
HTTP各个版本对比
网络·网络协议·http
auxor2 小时前
Android 窗口管理 - 窗口添加过程分析Client端
android
Yang-Never3 小时前
Kotlin -> object声明和object表达式
android·java·开发语言·kotlin·android studio
小白马丶4 小时前
Jetpack Compose开发框架搭建
android·前端·android jetpack
攻城狮Talk4 小时前
FocusParkingView清除旧Window焦点
android
狂浪天涯4 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 8 | SurfaceFlinger 合成 (一)
android
Wgllss4 小时前
完整案例:Kotlin+Compose+Multiplatform跨平台之桌面端实现(二)
android·架构·android jetpack
深盾安全5 小时前
Android 16KB页面对齐介绍
android
智江鹏5 小时前
Android 之 Kotlin 和 MVVM 架构的 Android 登录示例
android·开发语言·kotlin