本文Demo的完整工程代码, 客户端基于iOS实现, 参考这里的StudyHTTP, 服务器基于node.js实现, 参考这里的StudyHTTP
目录
URI, URL和URN
定义
URI(Uniform Resource Identifier, 统一资源标识符)用来唯一的标识一个资源
URL(Uniform Resource Locator, 统一资源定位器)可以用来标识一个资源, 而且还指明了如何locate这个资源
URN(Uniform Resource name, 统一资源命名)是通过名字来标识资源
关系和区别
- URL和URN都是一种URL
URI是以一种抽象的, 高层次概念定义统一资源标识, 而URL和URN则是具体的资源标识的方式
URI可以是绝对的也可以是相对的, 而URL则必须提供足够的信息来定位
RESTful API
什么是RESTful API?
首先要搞清楚什么是REST
REST(Representational State Transfer, 表现层状态转化)是一种网络架构规范
知道什么是REST, 就知道了什么是RESTful API
实现了REST规范的Web API就叫RESTful API
RESTful API有哪些特点?
- 总是使用HTTPS
或许这不属于REST架构, 但是却是最最重要和基础的一点, 详细可以参考本文的这一章HTTPS
- 将API的版本号放入URL
https://api.example.com/v1/
- 每个网址代表一种资源
所以网址中不能有动词, 只能有名词, 且使用复数
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
- HTTP动词对应资源的具体操作类型
GET(SELECT) 从服务器取出资源(一项或多项)
POST(CREATE) 在服务器新建一个资源
PUT(UPDATE) 在服务器更新资源(客户端提供改变后的完整资源)
PATCH(UPDATE) 在服务器更新资源(客户端提供改变的属性)
DELETE(DELETE) 从服务器删除资源
例子如下
GET /zoos 列出所有动物园
POST /zoos 新建一个动物园
GET /zoos/ID 获取某个指定动物园的信息
PUT /zoos/ID 更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID 更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID 删除某个动物园
更多RESTful API, 请参考RESTful API 设计指南
HTTP Header
Accept-Encoding
gzip - 使用gzip(GNU zip)压缩
compress - 使用Unix compress压缩
deflate - 使用zlib压缩
identity - 不进行编码
服务器开启压缩可以提高传输的效率, 详细可以参考expressjs/compression
Content-Type
常见的Content-Type有
application/json
text/json
application/json和text/json都是指json格式, 前者是官方规范, 后者是为了兼容, 详细参考What is the exact difference between content-type: text/json and application/json?
text/xml
text/html
text/plain
multipart/form-data
multipart/form-data用于POST上传文件时, 同时还需要设置boundary字段
在iOS开发中最有名的网络库AFNetworking中
- request serializers支持的Content-Type分别是
(1) AFJSONRequestSerializer
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
(2) AFPropertyListRequestSerializer
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
(3) AFHTTPRequestSerializer
application/x-www-form-urlencoded
(4) AFStreamingMultipartFormData
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
- response serializers支持的Content-Type分别是
(1) AFJSONResponseSerializer
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
(2) AFXMLParserResponseSerializer
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
(3) AFXMLDocumentResponseSerializer
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
(4) AFPropertyListResponseSerializer
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];
(5) AFImageResponseSerializer
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];
例如
在使用AFHTTPSessionManager的类构造方法+ (instancetype)manager生成对象时, 由于response serializer默认是AFJSONResponseSerializer
self.responseSerializer = [AFJSONResponseSerializer serializer];
如果response的Content-Type不是json类型的话, 那么就会出现类似下面的错误
2016-09-21 14:22:37.246 StudyHTTP[7589:623717] error = Error Domain=com.alamofire.error.serialization.response Code=-1016 "Request failed: unacceptable content-type: text/html" UserInfo={com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x610000225ba0> { URL: http://localhost:3000/users/ } { status code: 200, headers {
Connection = "keep-alive";
"Content-Length" = 23;
"Content-Type" = "text/html; charset=utf-8";
Date = "Wed, 21 Sep 2016 06:22:35 GMT";
Etag = "W/\"17-i6pE/Ux9hQaoN6ksprpWig\"";
"Proxy-Connection" = "Keep-alive";
"X-Powered-By" = Express;
} }, NSErrorFailingURLKey=http://localhost:3000/users/, com.alamofire.serialization.response.error.data=<72657370 6f6e6420 77697468 20612072 65736f75 726365>, NSLocalizedDescription=Request failed: unacceptable content-type: text/html}
Range
对于只需获取部分资源的范围请求, 包含首部字段Range即可告知服务器资源的指定范围
使用这个字段就可以实现文件的断点续传
User-Agent
将创建请求的浏览器和用户代理名称等信息传达给服务器
例如
当使用Safari浏览打开百度时
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Safari/602.1.50
关于更多Content-Type, 可以参考常用对照表 HTTP Content-type
HTTP Status Code
2XX 成功状态码 请求正常处理完毕
200 OK
201 Created
202 Accepted
204 No Content
206 Partial Content
3XX 重定向状态码 需要进行附加操作以完成请求
301 Moved Permanently(永久重定向)
302 Found(临时重定向)
4XX 客户端错误状态码 服务器无法处理请求
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
5XX 服务器错误状态码 服务器处理请求出错
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable
HTTP数据传递
GET方式的参数会添加到URL中
例如URL请求和参数设置如下(实现基于iOSAFNetworking)
[self.sessionManager GET:@"http://localhost:3000/users/" parameters:@{@"key": @"value"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"responseObject = %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"error = %@", error);
}];
那么HTTP请求实际的URL是
http://localhost:3000/users/?key=value
这时, 服务器通过request的query取出数据
console.log(req.query); // { key: 'value' }
POST方式的参数会添加到body中
例如URL请求和参数设置如下(实现基于iOSAFNetworking)
[self.sessionManager POST:@"http://localhost:3000/users/" parameters:@{@"key": @"value"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"responseObject = %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"error = %@", error);
}];
此时HTTP请求的body就是
key=value
这时, 服务器通过request的body取出数据
console.log(req.body); // { key: 'value' }
HTTPS
发展至今, HTTPS已经可以算是标配了, 当然中国的网络环境总是"慢人一步", 为什么要如此重视HTTPS呢?
这是因为HTTP存在以下问题
通信使用明文(不加密), 内容可能会被窃听
不验证通信方的身份, 因此有可能遭遇伪装
无法证明报文的完整性, 所以有可能已遭篡改
而HTTP加上加密处理和认证以及完整性保护后即是HTTPS
简单来说HTTPS(HTTP secure) = HTTP + 加密 + 认证 + 完整性保护
至于HTTPS为什么安全的具体分析, 可以参考SSL/TLS协议运行机制的概述
这里总结下HTTPS与HTTP的几个明显差异
HTTPS = HTTP over SSL/TLS (其中: SSL是Secure Sockets Layer的缩写, TLS是Transport Layer Security的缩写)
HTTPS需要到CA(Certificate Authority)申请证书
HTTP默认采用80端口, 而HTTPS默认采用443端口
HTTPS的简单流程是这样子的
客户端向服务器端索要并验证公钥
双方协商生成"对话密钥"
双方采用"对话密钥"进行加密通信
详细的过程可以参考图解SSL/TLS协议
参考
更多文章, 请支持我的个人博客