目录

前言 1.1

HTTP简介 1.2

HTTP学习目的 1.2.1

HTTP简介 1.2.2

HTTP内部流程 1.2.3

HTTP基本逻辑总结 1.2.4

HTTP详解 1.3

HTTP的典型结构 1.3.1

HTTP的Header头 1.3.2

HTTP的请求参数和编码 1.3.3

HTTP的响应的状态码 1.3.4

HTTP的响应数据格式JSON 1.3.5

HTTP相关 1.4

HTTP的工具和库 1.4.1

HTTP工具 1.4.1.1

HTTP库 1.4.1.2

HTTP的后台API设计 1.4.2

HTTP相关心得 1.4.3

HTTP的文件上传 1.4.4

混合内容 1.4.5

附录 1.5

优质教程 1.5.1

参考资料 1.5.2

前言

HTTP知识总结

最新版本：v3.4

更新时间：20231213

简介

总结HTTP技术相关内容。先进行了HTTP的简介，介绍了学习HTTP的目的，以及典型的HTTP的内部流程，以及总结了基本的逻辑。再进行HTTP的详细介绍，包括字段和结构，Header头，请求参数和编码，响应的状态码，响应数据的JSON格式等。再整理HTTP相关的一些内容，比如工具和不同语言的HTTP的网络库，以及如何进行后台HTTP相关的api接口设计，总结了一些相关心得，文件上传、混合内容等。最后附上一些HTTP相关的教程资料。

源码+浏览+下载

本书的各种源码、在线浏览地址、多种格式文件下载如下：

HonKit源码

crifan/http_summary: HTTP知识总结

如何使用此HonKit源码去生成发布为电子书

详见：crifan/honkit_template: demo how to use crifan honkit template and demo

在线浏览

HTTP知识总结 book.crifan.org

HTTP知识总结 crifan.github.io

离线下载阅读

HTTP知识总结 PDF

HTTP知识总结 ePub

HTTP知识总结 Mobi

版权和用途说明

此电子书教程的全部内容，如无特别说明，均为本人原创。其中部分内容参考自网络，均已备注了出处。如发现有侵权，请通过邮箱联系我 admin 艾特 crifan.com，我会尽快删除。谢谢合作。

各种技术类教程，仅作为学习和研究使用。请勿用于任何非法用途。如有非法用途，均与本人无关。

鸣谢

感谢我的老婆陈雪的包容理解和悉心照料，才使得我crifan有更多精力去专注技术专研和整理归纳出这些电子书和技术教程，特此鸣谢。

其他

作者的其他电子书

本人crifan还写了其他150+本电子书教程，感兴趣可移步至：

crifan/crifan_ebook_readme: Crifan的电子书的使用说明

关于作者

关于作者更多介绍，详见：

关于CrifanLi李茂 – 在路上

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:51:22

HTTP简介

HTTP简介

接下来先简要介绍一下HTTP的基础知识。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP学习目的

HTTP学习目的

写此HTTP相关内容的教程，期望能对不同类型的人员达到不同的目标：

普通用户用浏览器上网

了解打开网址到页面显示背后的故事

(iOS/Android)移动端开发

了解用代码调用后台API接口时

知道HTTP的有哪些方法，GET/POST等，知道其大概用途

如何传递GET和POST时候的参数

GET时的query string

POST时的body的json

学习用Postman等工具去测试后台提供的接口

后台人员设计API接口

如何设计一个RESTful的API

减少不良风格的接口

比如：/getUser, /updateUser

学习用Postman等工具去测试自己写的API接口

学习如何用Postman等工具去生成API文档

测试人员

充分利用Postman等工具去实现接口的自动化测试

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP简介

HTTP简介

HTTP=Hyper Text Transfer Protocol=超文本传输协议

HTTP的总体思路是：

客户端：向服务器发送一个请求=Request，请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。

服务器：以一个状态行作出响应=Response，相应的内容包括消息协议的版本，成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。

简而言之就是一个你问我答的协议：

客户端Client 问=请求=Request

服务器Server 答=响应=Response

关于HTTP的相关的详细知识，或许很多人不是很熟悉。但是：

作为普通电脑用户的你，经常用浏览器（IE/Chrome/Firefox等）去浏览网页时

作为程序开发，用网络库去调用服务器后台提供的接口时其实内部都用到了HTTP的技术。

比如一些典型情况：

普通用户用浏览器去查看网页

比如用户在用浏览器去打开网址：

http://www.baidu.com

去查看网页内容：

程序员写代码去调用后台接口

比如 （卓越一线的iOS的）swift代码中利用Alamofire的HTTP网络库，去实现：

传递用户名ID+密码的参数和POST类型，调用Alamofire发送请求，完成用户的登录

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP内部流程

HTTP内部流程

其实内部都是通用的HTTP的逻辑：

发送HTTP的请求

接受到响应后

解析并显示出对应的内容

解析得到所需要的数据

那如何才能看到内部的HTTP的请求和响应到底是什么样的呢？有很多方式可以实现查看内部到底发送了什么样的HTTP请求，和接收到了什么样的响应：

对于浏览器访问网页

可以用Chrome/Safari/Firefox等浏览器自带的调试工具：开发者工具

可以用其他工具去模拟

比如：命令行工具curl

对于写代码调用后台接口

可以用postman去模拟和查看

下面详细介绍如何查看内部的具体流程：

用Chrome的开发者工具去查看

先去打开：右上角 三个点-》更多工具-》开发者工具

点击到Network列，然后再去访问地址 www.baidu.com

就可以看到会有一堆的内容列出来，点击第一个 www.baidu.com 的Header

即可看到，对应的Request和Response的信息：

通过点击Request Headers和Response Headers的view source可以看到未被解析之前的原始内容：

对应的原始内容为：

Request请求：

GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: BAIDUID=6720BF4EAED8C8448EB6540DAF83BE56:FG=1; BIDUPSID=6720BF4EAED8C8448EB6540DAF83BE56; PSTM=1449798993; MCITY=-224%3A; BD_CK_SAM=1; PSINO=3; BD_HOME=0; H_PS_PSSID=1430_24568_21116_24880_20929; BD_UPN=123253

Response响应：

HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0xd4bdc47c00000909
Bduserid: 0
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Cxy_all: baidu+1ea6be4d211f802306f9e10940507eba
Date: Mon, 13 Nov 2017 01:32:41 GMT
Expires: Mon, 13 Nov 2017 01:32:06 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=0; path=/
Set-Cookie: H_PS_PSSID=1430_24568_21116_24880_20929; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Vary: Accept-Encoding
X-Powered-By: HPHP
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked

用curl工具去模拟浏览器访问百度的过程

在命令行中输入：

curl -v www.baidu.com

返回的结果是：

➜  ~ curl -v www.baidu.com
* Rebuilt URL to: www.baidu.com/
*   Trying 180.97.33.107...
* TCP_NODELAY set
* Connected to www.baidu.com (180.97.33.107) port 80 (#0)
> GET / HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: bfe/1.0.8.18
< Date: Mon, 13 Nov 2017 01:35:55 GMT
< Content-Type: text/html
< Content-Length: 2381
< Last-Modified: Mon, 23 Jan 2017 13:28:24 GMT
< Connection: Keep-Alive
< ETag: "588604f8-94d"
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Pragma: no-cache
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下，你就知道</title></head>; <body link=#0000cc> <div id=wrapper> <div id=head> .............. <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif>; </p> </div> </div> </div> </body> </html>
* Connection #0 to host www.baidu.com left intact

用postman去模拟前面的调用后台接口的登录过程

其中内部发送的HTTP请求，可以通过Code查看：

POST /v1/skr/login HTTP/1.1
Host: 123.206.101.36:9090
Content-Type: application/json; charset=UTF-8
authorization: Bearer mum5emp17q28st6v36ajltfvf3
Cache-Control: no-cache
Postman-Token: deb6784d-3689-32ce-ac01-687c22ad1b6e

{
    "user" : "13429",
    "password" : "666666"
}

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP基本逻辑总结

HTTP基本逻辑总结

上述的所有用浏览器访问网页，用代码调用后台接口去登录等等，其内部的HTTP的基本逻辑和核心流程都是一样的：

（客户端）发送请求Request

HTTP协议规定request的格式：

此处：

GET / HTTP/1.1
...
User-Agent: curl/7.54.0
Accept: */*

其中：

GET：方法类型，HTTP/1.1表示HTTP协议的版本是1.1

使用第三方库时，也需要制定对应的GET／POST／。。。

现在多数的HTTP协议都是使用的1.1

User-Agent：用户代理，表示自己的客户端的类型是curl

使用第三方库时，一般不需要设置此参数

第三方库会自动帮你设置

如果是浏览器的话，不同浏览器对应不同的预设值

比如上面的Chrome此处是：

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36

Accept：声明自己接收的信息的类型为/，表示所有类型内容都运行

如果调用服务器接口时，往往设置为：application/json

获得（服务器返回的）响应Response

HTTP协议规定的响应的格式：

举例：

此处的：

< HTTP/1.1 200 OK
。。。
< Content-Type: text/html
< Content-Length: 2381
。。。。
<
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8>........ </body> </html>
* Connection #0 to host www.baidu.com left intact

其中：

HTTP/1.1 200 OK：表示1.1版本的HTTP协议，返回状态为200，表示正常

其他典型的，不正常的，有问题的状态有很多，比如404找不到，500服务器内部错误等等

Content-Type: text/html：内容的类型为html

Content-Length: 2381：内容的长度为2381字节

<!DOCTYPE html>...：html的内容，浏览器加载此内容后，即可显示出你所看到的网页

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP详解

HTTP详解

接下来详细解释HTTP的结构和组成。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的典型结构

HTTP的典型结构

Request

URL

Method

GET

获取信息

不会更改服务器内部的数据

POST

向服务器发送数据：

创建xxx

更新xxx

准确的说，其实应该用PUT或UPDATE

会更改服务器内部的数据

PUT

PATCH

DELETE

HEAD：获取资源的元数据

OPTIONS：获取信息，关于资源的哪些属性是客户端可以改变的

Query Parameters

for GET

query string=qs=query parameters=params=query component

/xxx/xxx?key1=value1&key2=value2

for POST

body

JSON

url encoded

key1=value1&key2=value2

Headers

Content-Type

application/json

application/json;charset=UTF-8

Accept

application/json

Authorization

Bearer 725a7c44b76c45ab95152bcee014ae6e_1

Other

Cookie

LocalStorage

SessionStorage

响应Response

Status

Informational - 1xx

100 CONTINUE

Successful - 2xx

200 OK

Redirection - 3xx

301 MOVED_PERMANENTLY

Client Error - 4xx

400 BAD_REQUEST

401 UNAUTHORIZED

403 FORBIDDEN

404 NOT_FOUND

Server Error - 5xx

500 INTERNAL_SERVER_ERROR

Body

raw

to json

Other

Cookie

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的Header头

HTTP的Header头

HTTP的Header包括两类：

请求头=Request Header

响应头=Response Header

HTTP的Header方面的逻辑总结

既然是Client问, Sever答的过程，那么基本逻辑就是：

Client告诉服务器端，自己的Request请求，能够接受的各种信息是什么类型的

所以Request中有很多Accept方面的请求头

Accept：能接受（返回）哪些类型

格式：type/sub-type

*/* 表示任何类型

举例：

Accept: application/json

Accept: text/plain

Accept: text/html

Accept: image/jpeg

Accept: application/msword

Accept: image/png

Accept: application/pdf

Accept-Charset

能接受的字符集

Accept-Encoding

能接受的（编码）压缩类型

比如：gzip，deflate

Accept-Language

能接受的语言类型

以及其他一些额外的请求和希望

User-Agent:告诉你我是哪种浏览器（所处的操作系统是什么类型）等信息

举例：

User-Agent：Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN;rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14

User-Agent：Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36

User-Agent：Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5

Referer：告诉服务器是从（参考）哪个链接去访问的

举例：Referer： https://www.google.co.uk/

Keep-Alive：希望服务器保持此次连接（多长时间）

Cache-Control：表示是否使用缓存

Connection：完成本次请求的响应后，是否断开连接，是否要等待本次连接的后续请求了

Connection: close

Connection: keepalive

Sever返回响应，告诉客户端，自己的响应中数据都是什么类型的

所以Response中有很多Content方面的字段，表示服务器返回的内容的各种类型说明

Content-Encoding：此响应中使用了什么压缩方法（gzip，deflate）压缩响应中的对象

表示客户端的你用什么对应的方法去解压缩，才能得到数据的原文

举例：Content-Encoding：gzip

Content-Language：用了什么语言

Content-Length：数据内容的长度，单位：字节

举例：Content-Length: 2684

Content-Type：自己返回的数据是什么格式的

和Request中的Accept对应

举例：Content-Type：application/json

Connection：和Request对应

Connection: close

Connection: keepalive

以及其他一些表示当前信息的情况的

Location：一个url地址，表示你想要的资源被转移到别处了，

典型的是发生了302表示自动跳转，告诉你要的东西，换了地址了

常见Header举例

Request Header请求头

Accept

代码调用API时：

Accept: application/json

Accept: application/json; charset=UTF-8

浏览器访问网页时：

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8

Content-Type

代码调用API时：

Content-Type:application/json

Content-Type:application/json; charset=UTF-8

Content-Type:application/x-www-form-urlencoded

Authorization

代码调用API时：

Authorization: Bearer 6k8p8ucshobav531lftkb2f1bv

User-Agent

浏览器访问网页时：

User-Agent: Mozilla/5.0(Linux;X11)

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36

Accept-Language

浏览器访问网页时：

Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

Accept-Encoding

浏览器访问网页时：

Accept-Encoding: gzip, deflate, br

Response Header响应头

Content-Type

代码调用API时：

Content-Type:application/json

浏览器访问网页时：

Content-Type: text/html; charset=utf-8

Connection

浏览器访问网页时：

Connection: Keep-Alive

Content-Encoding

浏览器访问网页时：

Content-Encoding: gzip

更全更完整的Header

Request的Header

Header | 解释 | 示例
Accept | 指定客户端能够接收的内容类型 | Accept:text/plain,text/html
Accept-Charset | 浏览器可以接受的字符编码集。 | Accept-Charset:iso-8859-5
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型。 | Accept-Encoding:compress,gzip
Accept-Language | 浏览器可接受的语言 | Accept-Language:en,zh
Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges:bytes
Authorization | HTTP授权的授权证书 | Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control:no-cache
Connection | 表示是否需要持久连接。（HTTP 1.1默认进行持久连接） | Connection:close
Cookie | HTTP请求发送时，会把保存在该请求域名下的所有cookie值一起发送给web服务器。 | Cookie:$Version=1;Skin=new;
Content-Length | 请求的内容长度 | Content-Length:348
Content-Type | 请求的与实体对应的MIME信息 | Content-Type:application/x-www-form-urlencoded
Date | 请求发送的日期和时间 | Date:Tue,15 Nov 2010 08:12:31 GMT
Expect | 请求的特定的服务器行为 | Expect:100-continue
From | 发出请求的用户的Email | From: user@email.com
Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com
If-Match | 只有请求内容与实体相匹配才有效 | If-Match:“737060cd8c284d8af7ad3082f209582d”
If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功，未被修改则返回304代码 | If-Modified-Since:Sat,29 Oct 2010 19:43:31 GMT
If-None-Match | 如果内容未改变返回304代码，参数为服务器先前发送的Etag，与服务器回应的Etag比较判断是否改变 | If-None-Match:“737060cd8c284d8af7ad3082f209582d”
If-Range | 如果实体未改变，服务器发送客户端丢失的部分，否则发送整个实体。参数也为Etag | If-Range:“737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since:Sat,29 Oct 2010 19:43:31 GMT
Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards:10
Pragma | 用来包含实现特定的指令 | Pragma:no-cache
Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range | 只请求实体的一部分，指定范围 | Range:bytes=500-999
Referer | 先前网页的地址，当前请求网页紧随其后,即来路 | Referer:http:
TE | 客户端愿意接受的传输编码，并通知服务器接受接受尾加头信息 | TE:trailers,deflate;q=0.5
Upgrade | 向服务器指定某种传输协议以便服务器进行转换（如果支持） | Upgrade:HTTP/2.0,SHTTP/1.3,IRC/6.9,RTA/x11
User-Agent | User-Agent的内容包含发出请求的用户信息 | User-Agent:Mozilla/5.0(Linux;X11)
Via | 通知中间网关或代理服务器地址，通信协议 | Via:1.0 fred,1.1 nowhere.com (Apache/1.1)
Warning | 关于消息实体的警告信息 | Warn:199 Miscellaneous warning

Response的Header

Header | 解释 | 示例
Accept-Ranges | 表明服务器是否支持指定范围请求及哪种类型的分段请求 | Accept-Ranges: bytes
Age | 从原始服务器到代理缓存形成的估算时间（以秒计，非负） | Age: 12
Allow | 对某网络资源的有效的请求行为，不允许则返回405 | Allow: GET, HEAD
Cache-Control | 告诉所有的缓存机制是否可以缓存及哪种类型 | Cache-Control: no-cache
Content-Encoding | web服务器支持的返回内容压缩编码类型。 | Content-Encoding: gzip
Content-Language | 响应体的语言 | Content-Language: en,zh
Content-Length | 响应体的长度 | Content-Length: 348
Content-Location | 请求资源可替代的备用的另一地址 | Content-Location: /index.htm
Content-MD5 | 返回资源的MD5校验值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range | 在整个返回体中本部分的字节位置 | Content-Range: bytes 21010-47021/47022
Content-Type | 返回内容的MIME类型 | Content-Type: text/html; charset=utf-8
Date | 原始服务器消息发出的时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag | 请求变量的实体标签的当前值 | ETag: “737060cd8c284d8af7ad3082f209582d”
Expires | 响应过期的日期和时间 | Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified | 请求资源的最后修改时间 | Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location | 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 | Location:http://www.zcmhi.com/archives/94.html
Pragma | 包括实现特定的指令，它可应用到响应链上的任何接收方 | Pragma: no-cache
Proxy-Authenticate | 它指出认证方案和可应用到代理的该URL上的参数 | Proxy-Authenticate: Basic
refresh | 应用于重定向或一个新的资源被创造，在5秒之后重定向（由网景提出，被大部分浏览器支持） | Refresh: 5; url=http://www.zcmhi.com/archives/94.html
Retry-After | 如果实体暂时不可取，通知客户端在指定时间之后再次尝试 | Retry-After: 120
Server | web服务器软件名称 | Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie | 设置Http Cookie | Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer | 指出头域在分块传输编码的尾部存在 | Trailer: Max-Forwards
Transfer-Encoding | 文件传输编码 | Transfer-Encoding:chunked
Vary | 告诉下游代理是使用缓存响应还是从原始服务器请求 | Vary: *
Via | 告知代理客户端响应是通过哪里发送的 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning | 警告实体可能存在的问题 | Warning: 199 Miscellaneous warning
WWW-Authenticate | 表明客户端请求实体应该使用的授权方案 | WWW-Authenticate: Basic

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的请求参数和编码

HTTP的请求参数和参数编码

下面介绍HTTP的请求参数Request Parameters和请求参数的编码Request Parameters Encoding

GET的请求的参数：query string

关于Http的GET的请求参数，也有别的叫法：

请求参数=request parameter

= 查询=query

= 查询字符串=query string

= 请求参数=request parameters

= 参数序列化=param serialization=parameter serialization

转换后放到Url中的，是如下形式：

?key1=value1

?key1=value1&key2=value2

典型的是：

GET：参数想要放在url中以?key1=value1&key2=value2的形式

则有两种做法：

自己把参数组合成对应的格式，放到url中

把参数（字典，对象等）调用url encode函数去编码，生成对应的格式（再放到url中）

GET的一般特点

GET 请求有长度限制

所以query string一般被限制在1024个字节

超过限制则后台无法解析参数

GET 请求可被缓存

GET 请求保留在浏览器历史记录中

GET 请求可被收藏为书签

GET 请求不应在处理敏感数据时使用

GET 请求只应当用于取回数据

处理查询参数的库

JS的qs

js中http网络库：axios

其中有，处理查询参数的：qs

https://www.npmjs.com/package/qs

当需要处理get的query string时，就可以利用其中的Qs子库去处理

Qs.stringify(data)

实现：把（一般是字典类型==JSON类型的）变量，转换，字符串换，序列化，为&key1=value1之类的形式

关于别名：参数序列化

其中官网demo中：

  // `paramsSerializer` is an optional function in charge of serializing `params`
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

也说明了：

此处的stringify=字符串化，也叫做：paramsSerializer=`参数序列化

举例：

某js项目中用到了stringify：

import { stringify } from 'qs';

export async function queryRule(params) {
    return request(`/api/rule?${stringify(params)}`);
}

POST的请求的参数：post body

典型的是：把一堆的参数，放到post的body中，格式一般都是json格式

则一般也有两种做法：

自己把参数（对象，字典等），转换为对应的json字符串

调用库提供的方法去encode你的参数对象为json

其他一些解释：

POST的一般特点

POST 请求不会被缓存

POST 请求不会保留在浏览器历史记录中

POST 不能被收藏为书签

POST 请求对数据长度没有要求

GET和POST的请求的参数的编码

举例：

http库axios中create时支持的config中的paramsSerializer

就支持利用其他序列化的库，比如：

Javascript中的：

qs库

https://www.npmjs.com/package/qs

举例：

Qs.stringify(params, {arrayFormat: 'brackets'})

Alamofire中，对于get的参数支持url encoding，对于post支持json encoding

所以代码可以写成：

    var paramEncoding:ParameterEncoding = JSONEncoding.default
    if (httpMethod == .get) {
        paramEncoding = URLEncoding.default
    }

let curHttpReq = Alamofire.request(url, method: httpMethod, parameters: parameters, encoding: paramEncoding, headers: curHeaders)

然后从外部调用时，对于get的url参数，可以直接传递：

get中的url的参数的对象，让Alamofire内部去利用URLEncoding转换为对应的?key1=value1&key2=value2的格式：

var parameters = [String : AnyObject]()
parameters = [
    "planId":self.visitId as AnyObject,
    "name":self.missionInfo.missionInfo.name as AnyObject,
    "pics":pics as AnyObject,
    "publisher":gCurUserItem.userInfo.pkEmpno as AnyObject,
    "resultDescription":self.missionInfo.missionInfo.resultDescription as AnyObject,
    "finish":finishInt as AnyObject,
    "planProblem":planProblem as AnyObject
]

getUrlRespJson_async(
    httpMethod: hTTPMethod,
    url:  url,
    parameters: parameters,
    respJsonHandle: { [weak self] (response) in

另外，对应的对于POST来说，把json对象转换为json字符串的例子：

let parameters = [
    "id":self.missionInfo.missionProblem.id as AnyObject,
    "checkPlanId":self.checkPlanId as AnyObject,
    "checkResult":self.resultCell.textview.text as AnyObject,
    "pass":self.pass as AnyObject ,

] as [String : AnyObject]?

getUrlRespJson_async(
    httpMethod: .post,
    url:  ServerApi.getProblemIdResult(visitId: self.visitId,problemId: self.missionInfo.missionProblem.id),
    parameters: parameters ,
    respJsonHandle: { [weak self] (response) in

则内部的Alamofire就会把该参数对象，通过JSONEncoding转换为json字符串了。

举例：
python中，get中url参数可以利用urllib.urlencode（或urllib.quote_plus）去把dict字典转换为key=value的形式：

import urllib
params = urllib.urlencode({'spam': 1, 'eggs': 2, 'bacon': 0})
url = "http://www.musi-cal.com/cgi-bin/query?%s"; % params

关于HTTP参数编码的常见场景和问题

空格应该被encode编码为+还是%20？

如果你接触Python的url的encode比较多，可能会注意到一个现象：

好像空格有时候被编码为+，有时候被编码为%20，到底哪个才对？

Percent Encode也被叫做URL Encode
Percent Encode指的是，一些字符，在被（url）encode后，往往都是变成%xx
比如：
内部对应着HTTP请求时，类型被设置为：application/x-www-form-urlencoded

表格太长，分两个

! | # | $ | & | ' | ( | ) | * | +
%21 | %23 | %24 | %26 | %27 | %28 | %29 | %2A | %2B

和：

, | / | : | ; | = | ? | @ | [ | ]
%2C | %2F | %3A | %3B | %3D | %3F | %40 | %5B | %5D

而其他常见的字符被编码后的效果是：

newline | space | " | % | - | . | < | >
%0A or %0D or %0D%0A | %20 | %22 | %25 | %2D | %2E | %3C | %3E

和

\ | ^ | _ | ` | { | | | } | ~
%5C | %5E | %5F | %60 | %7B | %7C | %7D | %7E

即：

空格正常情况下被url encode=percent encode，的结果是%20

但是之所以有时候会看到+是因为：

历史上，最早的网页技术中，在表单form被（通过HTTP的GET／POST等请求，或者是邮件发送）提交时，键key和值value，都是被percent encode=url encode的

对应着类型是：application/x-www-form-urlencoded

但是后来有些变种的处理，其中就包括把空格space编码为+（而不是%20）

空格被编码的逻辑的历史

空格被url encode=percent encode，应该是：%20

而之前历史上有些变种的处理，会编码为：+

而Python中对于url encode相关的函数有3种，对应的效果分别如下：

#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib

paraValue = "Crifan Li"
queryPara = { "name" :  paraValue }
urlEncodedQueryPara = urllib.urlencode(queryPara)
print "urlEncodedQueryPara=%s"%(urlEncodedQueryPara) # urlEncodedQueryPara=name=Crifan+Li
quotedValue = urllib.quote(paraValue) # quotedValue=Crifan%20Li
quotedPlusValue = urllib.quote_plus(paraValue) # quotedPlusValue=Crifan+Li
print "quotedValue=%s,quotedPlusValue=%s"%(quotedValue, quotedPlusValue)

空格被编码

urllib.urlencode编码（字典中的）value

urllib.quote_plus编码字符串：空格编码为+

urllib.quote编码字符串：空格编码为%20

在url地址里包含中文时的编码显示和内部逻辑

背景：
如果url地址中有非普通的ASCII字符串，理论上都是会被编码后，所以你看到的如果url地址中有中文，实际上打开都是%xx之类的地址
中文字符串和其他字符串编码逻辑是一样的。

对于中文类字符被url encode，有很多在线网站可以帮你实现，比如：

http://tool.chinaz.com/tools/urlencode.aspx

http://tool.oschina.net/encode?type=4

输入中文李茂就可以被（UTF-8）编码为：%e6%9d%8e%e8%8c%82

提示

一般网页地址中的字符编码都用的是UTF-8

上面编码也都是采用的UTF-8编码得到的结果

而如果想要换成别的编码，比如另一种常见的中文编码GB2312，则编码出来的是另外的结果：

同样字符串的不同编码的效果

李茂 -》 UTF-8编码后 -》%e6%9d%8e%e8%8c%82

李茂 -》 GB2312编码后 -》 %c0%ee%c3%af

为何浏览器中的地址栏中的url地址可以看到有中文，而不是url encode之后的%xx？

而你在浏览器中看到的中文地址其实是浏览器帮你解码后的中文
真正的url地址是%xx形式的被（UTF-8）编码后的地址
比如你在Chrome（或其他浏览器）中看到的地址中有中文：
https://github.com/imaidev/imaidev.github.io/wiki/wifi嗅探与客流统计

实际上内部真正的地址是：

https://github.com/imaidev/imaidev.github.io/wiki/wifi%E5%97%85%E6%8E%A2%E4%B8%8E%E5%AE%A2%E6%B5%81%E7%BB%9F%E8%AE%A1

-》你可以通过，在地址栏中右键-》复制

再粘贴到别的文本工具中，而得到看到真正的被编码后的地址：

类似的，把该地址去（利用前面说的在线网站帮忙）解码也可以得到同样的中文：

http://tool.oschina.net/encode?type=4

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的响应的状态码

HTTP的响应的状态码

Http状态码=HTTP Code

关于状态码的类别的含义的总体解释是：

状态码大类 | 表示的含义 | 客户端client要做的事 | 服务器端server要做的事
1xx | Informational 信息 | 啥都不用做，知道就好 | 告诉client，信息收到了，我后续会处理
2xx | Successful 成功 | 啥都不用做，知道就好 | 告诉client，请求已正确处理
3xx | Redirection 重定向 | 重新请求返回的新地址 -> 才能获取真正需要的数据 | 告诉client，你需要的内容，由于一些原因，比如地址已发生变化了，然后返回该内容的新地址
4xx | Client Error 客户端的错误 | 确保用正确的参数和信息正确，重新请求 | 告诉client，请求已正确处理
5xx | Server Error 服务器端的错误 | （一般来说）都无需啥操作 -> 往往需要服务器端改了bug后，重新发送请求 | 需要服务器Server端自己找到具体出了啥错 -> 往往是服务器端的代码的bug导致了出错

最常用的状态码及含义

Successful - 2xx：成功类，行为被成功地接受、理解和采纳

200=OK

服务器成功返回用户请求的数据

注意：（部分人）为了简化处理，POST创建成功后应该返回201的但也返回200

400=BAD REQUEST

错误的请求

注意：（部分人）往往为了简化处理，把属于401的没有权限和属于403的有权限但是权限不够，往往都返回404

404=NOT FOUND

找不到资源

注意：有些做法是，把属于401或403的原因，假装找不到而返回404

500=INTERNAL SERVER ERROR

服务器内部错误

最常见的原因是：服务器内部挂了

比如：你传递参数中有些参数是空，而导致后台代码无法解析，出现异常而崩溃

次常用的响应码及含义

Informational - 1xx：信息类，请求收到，继续处理

Successful - 2xx：成功类，行为被成功地接受、理解和采纳

201 CREATED

通过POST或PUT创建资源成功

通过response header中会返回新创建的资源的url连接

response body可能有信息，也可能为空

202 Accepted

服务器接受了此请求，但是待会才会执行

204 NO CONTENT

资源创建成功，但是没有返回内容

常用于DELETE或PUT操作的返回

Redirection - 3xx：重定向类，为了完成请求，必须进一步执行的动作

301 Moved

该资源的URI接口已经变了，变成别的接口了

response header中应该告诉新接口地址，比如：

URI: url地址

Client Error - 4xx：客户端错误类，请求包含语法错误或者请求无法实现

401 UNAUTHORIZED

没有权限访问该资源

典型情况：用户没有登录，没有获得对应的access token而直接访问某资源

403 FORBIDDEN

禁止访问

典型情况：虽然用户已登录，但是去更新/删除需要更高权限才能操作的资源

405 METHOD NOT ALLOWED

方法不允许

举例：比如某个资源不允许PATCH操作，但是你调用PATCH操作

406 - Not Acceptable

无效的请求

比如：请求中的部分参数是无效的

409 CONFLICT

有冲突

举例：新建一个用户，但是主键手机号和之前已有用户冲突

415 Unsupported Media Type

典型情况：请求的header有问题

422 Unprocessable Entity

Specify values for all required parameter fields

Server Error - 5xx：服务器错误类，服务器不能实现一种明显无效的请求

502 BAD GATEWAY

网管错误

503 SERVICE UNAVAILABLE

无服务

举例：比如服务器内部正在维护，暂时不提供服务。可以换个时间重试

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的响应数据格式JSON

HTTP的响应数据格式JSON

调用服务器API接口，常见返回内容格式是：JSON

http的response返回的内容，原始raw格式，都是字符串string，text的类型

如果想要把raw的text/string转换为json，则可以：

利用很多库自带的功能，把返回内容转换为JSON

iOS的Alamofire

Alamofire.request("https://httpbin.org/get").responseJSON { response in
  if let json = response.result.value {
        print("JSON: \(json)") // serialized json response
    }
}

Python的Requests

>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
u'{"type":"User"...'
>>> r.json()
{u'disk_usage': 368627, u'private_gists': 484, ...}

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP相关

HTTP相关

接下来介绍HTTP相关的一些心得，工具，后台API设计等内容。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的工具和库

HTTP的工具和库

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP工具

HTTP工具

此处整理HTTP相关的库和工具：

Postman

可用于服务器端和其他移动端等，调试接口数据返回是否正常

举例：比如用Postman去调试奶牛云的后台的登录接口：

更多内容详见另外的教程：API开发利器：Postman

Chrome的开发者工具Developer Tools

调试页面内容是否正常，包括布局，参数等等

详见：【总结】浏览器中的开发人员工具（IE9的F12和Chrome的Ctrl+Shift+I）-网页分析的利器

curl

模拟去请求服务器数据的命令行工具

HTTPie

后来从jobbole/awesome-python-cn: Python资源大全中文版中知道了还有个HTTPie

Github: jakubroztocil/httpie: Modern command line HTTP client

官网：HTTPie – command line HTTP client

简介：

HTTPie is a command line HTTP client with an intuitive UI, JSON support, syntax highlighting, wget-like downloads, plugins, and more

看了下，比curl更好用，更方便。值得推荐。

Httpbin

Httpbin 是一个网站：

http://httpbin.org/

用于测试http各方面的内容。

首页：

比如：

get

测试get方法：

http://httpbin.org/get

返回各种信息，包括headers

{
  "args": {},
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-cn",
    "Cookie": "_gauges_unique=1",
    "Host": "httpbin.org",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.2 Safari/605.1.15"
  },
  "origin": "222.92.130.218, 222.92.130.218",
  "url": "https://httpbin.org/get"
}

目的和作用：可以通过你的代码或工具中，去调用 http://httpbin.org/get ，从返回的值中，确认你的接口调用GET时的各种参数，是否符合你的预期

ip

去测试当前IP：

http://httpbin.org/ip

返回：

{
  "origin": "222.92.130.218, 222.92.130.218"
}

用途举例：

【已解决】PySpider中使用多贝云IP代理池实现每次请求IP都不同

中就利用了：

http://httpbin.org/ip

去获取当前（代理）的IP，来确认每次通过代理访问时，代理IP地址是否正确，代理是否真的生效了。

json

你想要测试，返回json数据，写代码去解析json

可以用：

http://httpbin.org/json

返回json数据供你解析和测试代码

同时，该网页上还有测试界面供你测试数据效果：

点击Execute后效果：

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP库

HTTP库

此处整理不同编程语言的HTTP方面的库：

iOS的swift／Obj-C

第三方

Alamofire

swift

SwiftHTTP

Python

内置

Python 3

https://docs.python.org/3/library/urllib.html

Python 2

urllib

https://docs.python.org/2/library/urllib.html

urllib2

https://docs.python.org/2/library/urllib2.html

第三方

Requests

详见独立教程

Python心得：http网络库

JavaScript

好用的

axios

https://github.com/axios/axios

其他的

Request

https://github.com/request/request

Web API的Fetch

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

SuperAgent

https://github.com/visionmedia/superagent

fetch

https://github.com/github/fetch

举例

  import ‘whatwg-fetch’;
      fetch(
      ‘http://xxxxx/getProgressData’,
      {
          method : ‘POST’,
          headers : {
          ‘Content-Type’: ‘application/x-www-form-urlencoded; charset=UTF-8’,
          ‘Accept’: ‘application/json’
          },
          body: ‘currTime=2017-08-25&orgCode=SK316005’
      }
      )
      .then((resp) => {
      console.log(resp);
      let respJson = resp.json(); //typeof(respJson)= object
      console.log(‘respJson=’, respJson, ‘typeof(respJson)=’, typeof(respJson));
      return respJson;
      })
      .then((respJsonDict) => {
      console.log(‘parsed respJsonDict=’, respJsonDict);
      });

效果

返回的是object对象，是Response类型，其中json()之后得到的是Promise对象，也就是可以直接拿来使用的json对象了，然后就可以进行后序数据处理了

C#

HttpWebRequest

crifan的lib的C#的http的部分

第 9 章 crifanLib.cs之Http

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的后台API设计

HTTP的后台API设计

HTTP的后台API设计常用规范：RESTful

关于RESTful，详见另外的教程：HTTP后台端：RESTful API接口设计

后台设计时记得要支持CORS

设计和开发后台的REST的API时，记得要添加CORS支持，否则前端调用接口容易出现各种和CORS方面的问题，导致无法使用接口。

关于CORS的详见，见另外的教程：CORS开发总结

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP相关心得

HTTP相关心得

iOS（swift）用Alamofire时，注意返回response时所处的线程

iOS（swift）对于http库，Alamofire的响应返回默认处于主的UI线程

-> 而很多耗时的操作，是不建议，不允许，在主线程中操作的

-> 所以我后来自己在Alamofire基础上封装的库，是用dispatchBackground_async让responseJSON返回后运行在后台线程

-> 避免大量的操作堵塞了UI的响应

-> 又由于iOS中只能在主线程UI线程中操作UI元素

-> 所以此时如果直接在Alamofire返回的地方去操作UI元素则会报错：Terminating app due to uncaught exception NSInternalInconsistencyException reason Only run on the main thread或UI API called from background thread xx must be used from main thread only

解决办法是：

对于每个Alamofire的返回的response时，自己根据需要，加上异步主线程，在其中处理UI操作的部分。

示例代码：

    func getUnreadCount(){
        let url = ServerApi.getUnreadCountUrl()

        getUrlRespJson_async(
            httpMethod: .get,
            url: url,
            parameters: nil,
            respJsonHandle: { (response) in
                if response.isSuccess {
                    if let count = response.successValue["count"].int {
                        gLog.debug("count:\(count)")
                        dispatchMain_async ({
                            if count <= 0{
                                self.tabBarItem.badgeValue = nil
                            } else if count >= 100 {
                                self.tabBarItem.badgeValue = "99+"
                            } else {
                                self.tabBarItem.badgeValue = "\(count)"
                            }
                        })
                    }
                } else if response.isFailure {
                    dispatchMain_async ({
                        self.noticeInfo(response.failedMessage)
                    })
                }
        })
    }

在respJsonHandle中当isSuccess时，用dispatchMain_async确保处于主线程，然后才能去操作UI中的元素：self.tabBarItem.badgeValue

而对于上述函数详见：

http函数CrifanLibHttp.swift中的getUrlRespJson_async

线程函数CrifanThread.swift中的dispatchBackground_async

相关帖子可参考：

［已解决］swift中Alamofire的request的responseJSON不执行不返回了

断点续传就是利用Http的Range实现的

对于断点续传功能的实现，就是利用了HTTP的头Range去实现的：

详见：

［已解决］swift 下载时支持断点续传

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

HTTP的文件上传

HTTP的文件上传

在HTTP相关开发期间，往往会涉及到上传文件，其中会涉及multipart/form-data。

而官方的HTTP和multipart/form-data相关的协议有：

Returning Values from Forms: multipart/form-data

Format of Internet Message Bodies

Form submission

对于用HTTP的multipart/form-data实现文件上传：

既有合适的如Postman等工具方便去调试

也有iOS中Alamofire等库很好的支持

（如果清楚协议详情）也可以自己手动写代码拼接出multipart/form-data所需要的字段和内容去实现文件上传

下面就来详细介绍这方面的内容。

iOS中Alamofire支持文件上传的MultipartFormData

可以基本实现文件上传的iOS的Alamofire的代码：

func uploadImageData(imageData:NSData){
    dispatchBackground_async({
        gLog.verbose("imageData.length=\(imageData.length)")

        self.pleaseWait()

        var curHeader = Dictionary<String, String>()
        curHeader["authenticate"] = "token " + gCurUserItem.accessToken

        gLog.verbose("curHeader=\(curHeader)")

        let curDate = NSDate()

        let name = "file"
        let filename = curDate.toString("yyyyMMdd_hhmmss") + ".jpg"
        gLog.verbose("name=\(name), filename=\(filename)")
        //uploadImageData > name=file, filename=20160625_085958.jpg

        Alamofire.upload(
            .POST,
            ServerApi.uploadFileUrl(gCurUserItem.id),
            headers: curHeader,
            multipartFormData: {multipartFormData in
                multipartFormData.appendBodyPart(
                    data: imageData,
                    name: name,
                    fileName: filename,
                    mimeType: "image/jpeg"
                )
            },

            encodingCompletion: { encodingResult in
                gLog.verbose("encodingResult=\(encodingResult)")
                /*
encodingResult=Success($ curl -i \
    -X POST \
    -H "User-Agent: Xxx App/com.xxx.YyyApp (2016.6.24; OS ban ben 9.3(ban hao 13E230))" \
    -H "authenticate: token zzzzzz" \
    -H "Content-Type: multipart/form-data; boundary=alamofire.boundary.5fd5efc56016a4df" \
    -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \
    -H "Accept-Language: en-US;q=1.0, zh-Hans-US;q=0.9" \
    "http://192.168.1.102:8080/app/user/10000010/upload", false, nil)

encodingResult=Success($ curl -i \
    -X POST \
    -H "User-Agent: Xxx App/com.xxx.YyyApp (2016.6.24; OS ban ben 9.3(ban hao 13E230))" \
    -H "authenticate: token zzzzzz" \
    -H "Content-Type: multipart/form-data; boundary=alamofire.boundary.01fc8db6b551cd51" \
    -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \
    -H "Accept-Language: en-US;q=1.0, zh-Hans-US;q=0.9" \
    "http://xxx.yyy.cn/app/user/10000010/upload", false, nil)
                 */

                switch encodingResult {
                //case .Success(let uploadRequest, streamingFromDisk, streamFileURL):
                    case .Success(let uploadRequest, _, _):
//                        gLog.verbose("uploadRequest=\(uploadRequest), streamingFromDisk=\(streamingFromDisk), streamFileURL=\(streamFileURL)")
                        gLog.verbose("uploadRequest=\(uploadRequest)")
                    //upload=POST http://192.168.1.102:8080/app/user/10000010/upload
                    //uploadRequest=POST http://xxx.yyy.cn/app/user/10000010/upload

                    uploadRequest.responseJSON(completionHandler:
                        { response in
                            gLog.verbose("response=\(response)")
                            /*
                             response=SUCCESS: {
                                code = 200;
                                data =     {
                                    created = 1466850689624;
                                    id = 1000001020160625103129624;
                                    location = "<null>";
                                    name = "image.jpg";
                                    owner = "<null>";
                                    uploader = 10000010;
                                };
                                message = ok;
                            }

                            response=SUCCESS: {
                                errors =     (
                                    "The request body may not be null",
                                    "The request body may not be null"
                                );
                            }

                            response=SUCCESS: {
                                code = 200;
                                data =     {
                                    created = 1466859610413;
                                    id = 1000001020160625130010413;
                                    location = "<null>";
                                    name = "20160625_085958.jpg";
                                    owner = "<null>";
                                    uploader = 10000010;
                                };
                                message = ok;
                            }
                             */

                            switch response.result {
                            case .Success(let value):
                                let valueJson = JSON(value)
                                gLog.verbose("\(valueJson)")
                                /*
                                {
                                  "message" : "ok",
                                  "code" : 200,
                                  "data" : {
                                    "name" : "image.jpg",
                                    "id" : "1000001020160625103129624",
                                    "uploader" : 10000010,
                                    "location" : null,
                                    "created" : 1466850689624,
                                    "owner" : null
                                  }
                                }
                                 */

                                let statusCode = valueJson["code"].int ?? 0
                                if statusCode == 200 {
                                    let dataObj = valueJson["data"].object
                                    let dataJson:JSON = JSON(dataObj)
                                    self.uploadAttachmentsHandler(Alamofire.Result.Success(dataJson))
                                    gLog.verbose("dataJson=\(dataJson)")

                                } else {
                                    self.noticeError("上传附件出错", autoClear: true, autoClearTime: 1)

                                    let message = valueJson["message"].string ?? ""
                                    let messageStrArr = message.splitToStrArr("|")

                                    var messageStr:String = message
                                    var subCode:Int = 0

                                    if messageStrArr.count > 1 {
                                        messageStr = messageStrArr[0]
                                        subCode = Int(messageStrArr[1]) ?? 0
                                    }

                                    let error:NSError = NSError(domain: HttpErrorDomain, code: statusCode, userInfo: [
                                        "message"   : messageStr,
                                        "code"      : statusCode,
                                        "subCode"   : subCode,
                                        ])

                                    self.uploadAttachmentsHandler(Alamofire.Result.Failure(error))
                                }
                            case .Failure(let error):
                                gLog.verbose("error=\(error)")
                                self.uploadAttachmentsHandler(Alamofire.Result.Failure(error))
                            }
                    })

                case .Failure(let encodingError):
                    gLog.error("上传附件失败\(encodingError)")
                }
            }
        )

    })
}

详见：

［已解决］Alamofire中multipartFormData中的name和filename到底应该是什么值

Postman中可以方便的调试文件上传

当然，如果后台设计的不是很规范，则可能导致，即使传入了标准的参数设置：

POST xxx/xxx/upload

Headers

Authorization: Bearer 6c9f198878cb42e6aad83fecbb598d10_8a9bfdab5a21ca27015a21d72fb20000

Content-Type: multipart/form-data;boundary=any_random_value

Accept: application/json

却仍可能会返回错误HTTP 415 Unsupported Media Type

详见：

【已解决】Mac中Postman去POST实现文件上传时出错：HTTP 415 Unsupported Media Type – 在路上

但是本身Postman对于文件上传的支持，还是很方便很好用的。

C#中如何手动裸写multipart/form-data的文件上传

之前也曾折腾过，自己用C#代码拼凑出multipart/form-data所需要的内容的格式。

如此折腾后，算是对于multipart/form-data有了更深入的认识。

详见：

【教程】模拟登陆之如何分析并用代码模拟上传文件 – 在路上

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:46:23

混合内容

混合内容

当很多网站提高安全性，而把http的域名，都改为https了。

但是如果在此期间网站内部没有完全处理好，残留部分http的链接，则就会出现：

Mixed Content=混合内容=http网页中加载了http的内容/资源

不同浏览器对Mixed Content 有不一样的处理规则。

此处暂时不需要深究。知道有这个事情即可。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

附录

附录

整理出一些教程、参考资料等内容供参考。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

优质教程

优质教程

HTTP 协议入门 - 阮一峰的网络日志

curl网站开发指南 - 阮一峰的网络日志

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36

参考资料

参考资料

【已解决】PySpider中使用多贝云IP代理池实现每次请求IP都不同

【整理Book】Web前端三剑客：HTML+CSS+JS

【未解决】给Axios的http请求添加公共处理部分

【整理】Httpbin免费提供HTTP请求和响应的测试网站

【已解决】ReactJS中好用的获取网络数据的http库

CORS开发总结

【已解决】JS中如何实现将字典变量去url的encode

【已解决】iOS中Alamofire参数解析JSONEncoding出错 – 在路上

URL encoding the space character: + or %20? - Stack Overflow

Percent-encoding - Wikipedia

GitHub - Alamofire/Alamofire: Elegant HTTP Networking in Swift

GitHub - requests/requests: Python HTTP Requests for Humans™ ✨🍰✨

python中 urllib, urllib2, httplib, httplib2 几个库的区别

21.11. http — HTTP modules — Python 3.6.3rc1 documentation

HTTP请求与响应的知识点总结 - 简书

20.5. urllib — Open arbitrary resources by URL — Python 2.7.14 documentation

HTTP请求响应报文&&相关状态码&&GET_POST请求方法 总结

url encoding - How to urlencode a querystring in Python? - Stack Overflow

HTTP协议详细总结 - 周东尧的个人页面

Get Started - PayPal Developer

Status Codes - Flask API

Status codes in HTTP

HTTP头部详解-zooyo-ChinaUnix博客

HTTP头信息详解-转 - chuncn - 博客园

HTTP Header 详解_知识库_博客园

HTTP 方法：GET 对比 POST

List of HTTP header fields - Wikipedia, the free encyclopedia

HTTP 教程 | 菜鸟教程

HTTP Header 详解_知识库_博客园

HTTP/1.1: Header Field Definitions

Requests Header | Http Header

HTTP content-type | 菜鸟教程

403 Forbidden vs 401 Unauthorized HTTP responses - Stack Overflow

HTTP/1.1: Status Code Definitions

Fetch Standard

Mixed Content

什么是混合内容？|Web | Google Developers

关于启用 HTTPS 的一些经验分享（一） | JerryQu 的小站

混合内容 - 安全 | MDN

XTM Connect SDK – REST API

Vue乱搞系列之axios发起表单请求 - 简书

Vue.js 学习系列三——axios 学习实践 - 前端 - 掘金

axios/axios: Promise based HTTP client for the browser and node.js

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2023-12-13 15:43:36