HTTP 协议基础

发布于:2025-05-20 ⋅ 阅读:(12) ⋅ 点赞:(0)

本篇文章会从如下角度介绍 HTTP 协议:

  • 原理与工作机制
  • 请求方法与状态码
  • Header 与 Body

1、原理与工作机制

1.1 HTTP 是什么

HyperText Transfer Protocol,超文本传输协议,"超"表示扩展而非超级,即可以链接到其他文本的扩展型文本。典型代表是 HTML 文档,其中包含标题、段落等结构化元素和超链接功能。最初 HTTP 就是为传输 HTML 而设计,它可以解决资源定位问题,确保客户端能获取特定资源。

1.2 HTTP 的工作方式

基本流程:浏览器地址栏输入 URL → 发送请求到服务器 → 服务器处理请求 → 返回响应 → 浏览器渲染显示。

以上流程涉及到两个核心组件:

  1. 渲染引擎:负责将 HTML 文本转换为可视内容(如 Chrome 使用 Blink,Safari 用 WebKit,Firefox 用 Gecko)
  2. 服务器交互:实际工作包含请求 → 响应的完整周期,而非简单的地址栏输入

我们来了解 URL 如何转成 HTTP 报文,以及报文的格式。

URL 通常由协议类型、服务器地址、路径三部分组成:

请添加图片描述

上述地址可转换成如下精简形式的 HTTP 请求报文(精简请求头的部分内容):

在这里插入图片描述

完整的请求报文如下:

GET /search/users?q=google HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
User-Agent: PostmanRuntime-ApipostRuntime/1.1.0
Connection: keep-alive
Host: api.github.com

第一行是请求行,有三要素:

  • Method:操作类型(GET 获取 / POST 提交,如论坛发帖用 POST)
  • Path:资源定位路径(如 /search/users?q=google 定位用户资源)
  • HTTP 版本:主流为 1.1,2.0 在 API 服务中逐步普及

第二行到最后一行是请求头 Headers,每个 Header 都是键值对形式,多行之间无需空行。

当然,对于 POST 请求还会有请求体 Body,但是 GET 就没有,这部分后续会详解。

此外,服务器返回的内容称为响应体,它的大致格式如下:

在这里插入图片描述

从 Android 开发角度看,开发者通过 API 接口调用(如 Retrofit),底层自动完成报文转换,实际得到的是从完整响应报文中提取的有效载荷(如 JSON),非原始报文。

2、请求方法与状态码

2.1 请求方法

本节来介绍几个 HTTP 常用的请求方法。

GET

GET 是 HTTP/0.9 版本唯一的方法,它的核心功能是用于获取资源。它具备如下特征:

  • 绝对不包含请求体(Body)
  • 具有幂等特性(多次调用结果相同)

开发时要注意,HTTP 规范要求 GET 不能带 Body,但实际开发中可能遇到不规范设计。Retrofit 等框架会强制校验 GET 是否带 Body 这条规范,如果发送的 GET 请求带有 Body,就会抛出如下异常:

java.lang.IllegalArgumentException:Non-body HTTP method cannot contain @Body.

POST

POST 用于用于增加或修改资源,它具备如下特征:

  • 必须包含请求体(Body)
  • 不具有幂等性(多次调用可能产生多个资源)

POST 请求报文大致如下:

POST /users HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

name=Tony&gender=male

请求头 Headers 与请求体 Body 之间隔一个空行。Body 支持多种格式,如示例中的 x-www-form-urlencoded,具体格式由 Content-Type 头指定。

PUT

PUT 用于修改资源,它具备如下特征:

  • 必须包含请求体(Body)
  • 具有幂等特性(多次修改结果一致)

与 POST 的区分:

  • PUT 专用于修改,POST 还可用于新增
  • 实际开发中可互换使用,取决于后端设计

PUT 请求报文大致如下:

PUT /users/1 HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

gender=female

DELETE

用于删除资源,特征如下:

  • 不需要请求体(Body)
  • 通过 URL 路径定位要删除的资源
  • 具有幂等特性(首次删除成功,后续操作无效果)

DELETE 请求报文大致如下:

DELETE /users/1 HTTP/1.1
Host: api.github.com

HEAD

HEAD 的功能与 GET 几乎一致,唯一区别是 HEAD 不会返回响应体。

HEAD 主要用于获取信息。比如下载文件之前, 先用 HEAD 发一个请求,可以获得诸如文件大小、是否支持断点续传等信息, 根据这些信息可以做进一步决策,比如是否分段下载等等,最后使用 GET 去进行真正的文件下载。

2.2 状态码

状态码会对 HTTP 响应结果进行类型化描述,如 200 表示获取成功、404 表示内容未找到等。

状态码按首位数字分为 5 大类(1xx - 5xx),每类代表不同响应类型:

  • 1xx:临时性消息。如:100 (继续发送)、101(正在切换协议)
  • 2xx:成功。最典型的是 200(OK)、201(创建成功)
  • 3xx:重定向。如 301(永久移动)、302(暂时移动)、304(内容未改变)
  • 4xx:客户端错误。如 400(客户端请求错误)、401(认证失败)、403(被禁止)、404(找不到内容)
  • 5xx:服务器错误。如 500(服务器内部错误)

下面稍作解释。

1xx

1xx 是临时性消息,不包含最终响应,仅传递过渡信息。比较重要的是 100 与 101 两个状态码:

  • 100 Continue:请求体较大时的分段传输确认。客户端发送大文件时,请求头中会包含 Expect: 100-continue 字段,服务器接收到之后意识到客户端会继续发送数据,此时会先返回 100 告知客户端可以继续发送,直到客户端所有数据发送完,服务器全部处理好之后才返回 200
  • 101 Switching Protocols:协议升级(如 HTTP/1.1 → HTTP/2)。客户端发送请求时带有 Upgrade: h2c 字样,表示询问服务器是否支持 HTTP 2.0。如支持,则服务器返回 101,后续客户端会发送 HTTP 2.0 的相关请求; 否则会直接返回一个 200

2xx

所有 2 开头的状态码均表示请求成功处理,200 表示通用的成功状态,比如网页请求成功:

在这里插入图片描述

201 表示创建成功,如成功新建了用户资源。

3xx

重定向状态码,核心机制是通过 Location 头字段指定新资源地址。常用于网站改版、HTTPS 升级、临时维护等场景。

比如我们在浏览器中输入 http://www.baidu.com,观察请求结果会发现有两个访问 www.baidu.com 的请求,状态码分别是 307 与 200:

在这里插入图片描述

307 状态码对应的是原始请求:

在这里插入图片描述

由于状态码是 307 需要根据响应头的 Location 的值进行重定向,因此 http 请求被重定向到 https 请求:

在这里插入图片描述

进行 https 请求时状态码为 200 表示请求成功。

除了上面 307 这个内部重定向外,还有:

  • 301:永久重定向(SEO 权重转移)
  • 302:临时重定向(保留原地址权重)

需要注意的是,重定向是浏览器行为,它自动跳转到新地址,用户可能感知不到原始请求。

4xx

4 开头的状态码表示请求方(浏览器/客户端)导致的错误。由于请求方的错误导致服务器无法返回正常的内容,此时就会返回 4 开头的状态码,根据错误不同分为如下几种常见情况:

  • 401:未授权访问,需要登录后才可访问该内容
  • 404:请求了服务器不存在的内容,可能是提供了错误的 URL 导致的

5xx

表示服务端处理请求时发生的内部错误,常见原因:数据库连接失败、资源不足、程序异常等,典型代码有 500 表示通用服务器错误,502 表示网关错误。

将客户端与服务器错误分成两类状态是为了方便程序员调试,而不是给用户看的。

3、Header

虽然 Body 才是报文的核心,但是 Body 的内容是配合 Header 来写的,因此我们要先介绍 Header 的内容。

Header 是 HTTP 消息的 metadata(元数据,即数据的数据),用于描述核心数据(Method/Path/Body)的属性。比如指定 Body 的格式、长度、压缩方式等辅助信息,例如 Content-Type 决定 Body 的解析方式。

下面我们来介绍常用的 Header。

3.1 Host

⽬标主机。注意:Host 不是在⽹络上⽤于寻址的,⽽是在⽬标服务器上⽤于定位⼦服务器的。

比如一个 GET 请求的 Header 中,标明了 Host: api.github.com。这个 Host 地址并不用于网络寻址,因为网络寻址是通过 DNS 服务,由 Host 名字查询到对应的 IP 地址,然后通过 IP 地址查找到服务器。

虚拟主机可以在一个主机上运行多个服务器,比如我买一个阿里云服务器,在上面跑了四个服务器,那么 Host 的名字就用于告知,我要联系的是这个 IP 地址下哪一个具体的子服务器。如果这个阿里云服务器上只跑了一个子服务器,那么无需使用 Host 进行区分。

3.2 Content-Type 与 Content-Length

Content-Type 用于指定 Body 的类型,Content-Length 则是 Body 的长度(单位:字节)。比如:

POST /users HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

name=Tony&gender=male

Body 内容 name=Tony&gender=male 的长度就是 Content-Length 的 21。由于 Body 内容可以是二进制,为了避免二进制数据恰好与特殊的换行符或结束符,比如 \n 相同而造成数据截断,因此使用 Content-Length 告知 Body 的长度。

至于 Content-Type,主要有五类,逐一来看。

text/html

请求 Web ⻚⾯时返回响应的类型,Body 中返回 html ⽂本,说白了就是 html 页面。格式如下:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 853

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
 ...

application/x-www-form-urlencoded

Web 页面纯文本表单,也称为普通表单,形如下图:

在这里插入图片描述

代码如下,注意 enctype 的值就是 application/x-www-form-urlencoded

在这里插入图片描述

提交表单发送网络请求时,可以看到请求头中的 Content-Type 的值就是 application/x-www-form-urlencoded,而提交的表单内容则在请求体中:

POST /users HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

name=Tony&nickname=Iron Man

对于 Android 开发而言,使用 Retrofit 发送上述请求时,需要这样配置 Retrofit 接口函数:

@FormUrlEncoded
@POST("/users")
Call<User> addUser(@Field("name") String name, @Field("nickname") String nickname);

使用 @FormUrlEncoded 注解,Retrofit 在拼接请求时才会将 Content-Type 赋值为 application/x-www-form-urlencoded 这种普通表单格式。又由于表单提交是 POST 请求,所以要使用 @POST 注解,配合 @Field 注解将参数转换成键值对形式。

multipart/form-data

Web ⻚⾯含有⼆进制⽂件时的提交⽅式。使用 multipart 可以传递图片,这也是当前大厂传递图片普遍使用的方式。

比如 Web 页面现在可以上传图片文件:

请添加图片描述

提交时能看到 Content-Type 的值为 multipart/form-data,而且后面还跟了一个 boundary 分隔符:

POST /users HTTP/1.1
Host: test.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 2382

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"

Tony
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar";
filename="avatar.jpg"
Content-Type: image/jpeg

JFIFHHvOwX9jximQrWa......
------WebKitFormBoundary7MA4YWxkTrZu0gW--

含有二进制文件的表单,每个部分的数据需要用 boundary 这个分割线隔开。第一部分数据的 key 是 name,value 是 Tony;第二部分是图片数据,key 是 avatar,类型是 image/jpeg。

boundary 的值之所以这么长,原因是为了避免与发送的数据内容相同造成错误的数据分割。

对应的 Retrofit 代码:

@Multipart
@POST("/users")
Call<User> addUser(@Part("name") RequestBody name, @Part("avatar") RequestBody avatar);
...
RequestBody namePart = RequestBody.create(MediaType.parse("text/plain"), nameStr);
RequestBody avatarPart = RequestBody.create(MediaType.parse("image/jpeg"), avatarFile);
api.addUser(namePart, avatarPart);

multipart 表单请求必须要加 @Multipart 注解,并且每个 part 的数据由 @Part 注解指定。

application/json

JSON 形式,用于 Web Api 的响应或 POST / PUT 请求。这种格式在请求体与响应体中都会用到,比如在请求中提交 JSON:

POST /users HTTP/1.1
Host: test.com
Content-Type: application/json; charset=utf-8
Content-Length: 32

{"name":"Tony","gender":"male"}

对应的 Retrofit 代码:

@POST("/users")
Call<User> addUser(@Body("user") User user);
...
// 需要使⽤ JSON 相关的 Converter,如在配置 Retrofit 时添加 GsonConverterFactory
api.addUser(user);

响应中返回的 JSON 数据:

HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
content-length: 234

[{"login":"mojombo","id":1,"node_id":"MDQ6VXNlcjE=","avatar_url":"https://avatars0.githubusercontent.com/u/1?v=4","gravat......

通常需要配置 Retrofit 添加 GsonConverterFactory 将 JSON 格式的数据反序列化成对象:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

其他单文件类型

比如 image/jpeg、application/zip 这些单文件类型,也是用于 Web Api 的响应或 POST / PUT 请求。像网络上的图片,它的 Content-Type 就很有可能是 image/jpeg。

当你要上传一个图片时,除了前面讲过的 multipart 表单方式之外,也可以使用如下方式(当然还是用 multipart 的更多)上传图片:

POST /user/1/avatar HTTP/1.1
Host: test.com
Content-Type: image/jpeg
Content-Length: 1575

JFIFHH9......

对应的 Retrofit 代码:

@POST("users/{id}/avatar")
Call<User> updateAvatar(@Path("id") String id, @Body RequestBody avatar);
...
RequestBody avatarBody =RequestBody.create(MediaType.parse("image/jpeg"), avatarFile);
api.updateAvatar(id, avatarBody)

而服务器返回图片时,大致内容如下(类型是 image/jpeg,长度 1575,响应体是图片的二进制数据):

HTTP/1.1 200 OK
content-type: image/jpeg
content-length: 1575

JFIFHH9......

网站公告

今日签到

点亮在社区的每一天
去签到