HTTP协议细节

上一篇的 HTTP协议详解 主要偏向理论和入门,这篇文章会比较细地介绍其中的相关内容,主要涉及首部字段、请求与响应体等。

Content-Type

在做HTTP请求的时候,我们通常会添加首部字段Content-Type,服务端则通过Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。以下是容易混淆的几个字段:

  • multipart/form-data

在AFNetworking 中经常会使用下面的方法上传文件:

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

使用multipart/form-data时会把请求体分成多个块,多个块之间依赖于boundary值去做分割,AFNetworking 生成boundary 的代码如下:

static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

在生成的boundary的时候,要求它足够长,不能在字节流中重复出现,否则就会导致错误的传输。分块中如果某个块是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。主体信息生成之后,还会在首部字段里设置Content-Type的类型为multipart/form-data,以及本次请求的boundary内容。

以下是用AFNetworking上传文件的代码和抓包,乱码部分为图片二进制数据:

[manager POST:uploadURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        NSURL *url = [[NSBundle mainBundle] URLForResource:@"1.jpg" withExtension:nil];
        [formData appendPartWithFileURL:url name:@"file" error:nil];
    } progress:^(NSProgress * _Nonnull uploadProgress) {

    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@", [error localizedDescription]);
    }];
multipart/form-data.png

可以看出boundary=Boundary+B21C39B63D7ABC44,主题报文的只有一个分块,分块的文件类型为Content-Type: image/jpeg。

  • application/x-www-form-urlencoded

在AFNetworking中,如果我们用下面的方法上传文件:

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

那么最终就会以 application/x-www-form-urlencoded 的方式提交数据,它最大的特点就是会使用urlencoded对body内容编码。在AFNetworking中源码如下:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;
    if (parameters) {
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

以下是用AFNetworking上传文件的代码和抓包,乱码部分为图片二进制数据:

NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"1.jpg" withExtension:nil];
[manager POST:uploadURL parameters:@{@"file":[NSData dataWithContentsOfURL:fileURL]} progress:^(NSProgress * _Nonnull uploadProgress) {

} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"%@", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"%@", [error localizedDescription]);
}];
application/x-www-form-urlencoded.png

可以看见file字段进行了URL编码,这样在上传文件的时候,因为字节流中有许多非ASCII码,文件的长度会变至原本的2-3倍。
以下是一张图片经过x-www-form-urlencoded编码后的抓包数据,原图像91KB,编码上传的时候变为251KB。因此这种方式上传大文件的时候,流量会加大许多。

URL编码前.png
URL编码后.png
  • application/json

application/json 这个Content-Type作为响应头比较简单。它用来告诉服务端消息主体是序列化后的JSON字符串。

以下是用AFNetworking上传文件的代码和抓包,乱码部分为图片二进制数据:

manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager POST:uploadURL parameters:@{@"file":@"I am file"} progress:^(NSProgress * _Nonnull uploadProgress) {

} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"%@", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"%@", [error localizedDescription]);
}];
application/json.png
  • application/octet-stream

application/octet-stream 这个在AFNetworking中一般是指二进制数据,当一个文件的类型不能被识别的时候,一般会使用application/octet-stream。在AFNetworking中一般会配合multipart/form-data使用,因为使用multipart/form-data可以传输多个文件,每个文件在body分块由boundary分割,如果该分块是文件,需要指定文件名和文件类型信息,当一个文件的类型不能被识别的时候,就会使用application/octet-stream。

static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }
}

以下是上传1.data时的主题报文的的Content-Type:

application/octet-stream.png

在上传大文件时,如果使用application/x-www-form-urlencoded作为Content-Type,文件的长度会变至原本的2-3倍,这时最好使用multipart/form-data。在上传少量数据时,如果使用multipart/form-data由于存在boundary会带来额外的流量,这时最好使用application/x-www-form-urlencoded。至于使用哪个还是根据实际情况考虑。

Range

Range 属性在做断点续传的时候使用的比较多,表示请求资源的部分内容(不包括响应头的大小), 单位是byte,即字节,从0开始。如果服务器能够正常响应的话,服务器会返回206 Partial Content的状态码及说明。如果不能处理这种Range的话,就会返回整个资源以及响应状态码为200 OK。

请求 Range。

Range: bytes=100- 第100个字节及最后个字节的数据
Range: bytes=200-1000 第20个字节到第1000个字节之间的数据.
Range: bytes=-500  表示最后500个字节
Range: bytes=500-  表示500字节以后的范围
Range: bytes=0-0,-1 第一个和最后一个字节
Range: bytes=500-600,601-999 同时指定几个范围

响应 Range。

Content-Range: bytes 0-100/4000 服务器响应了前(0-100)个字节的数据,该资源一共有(4000)个字节大小。

ETag

被请求变量的实体值,用来判断当前请求资源是否改变,主要为了解决 Last-Modified 无法解决的一些问题,Last-Modified和If-Modified-Since只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性,类似于资源的MD5。

  • If-None-Match

在HTTP Response中添加ETags信息,当客户端再次请求该资源时,将在HTTP Request中加入If-None-Match信息(ETags的值)。如果服务器验证资源的ETags没有改变(该资源没有改变),将返回一个304状态;否则服务器将返回200状态,并返回该资源和新的ETags。

  • If-Match

在HTTP Request中加入If-Match信息(ETags的值)。如果服务器验证资源的ETags与If-Match的值一致,表示文件没有被修改,则开始继续传送文件,服务器返回 200 OK;2. 如果文件被修改,则不传输,服务器返回 412 Precondition failed

以下是对简书请求的响应报文:

响应报文.png

我们可以拿到 ETags再去请求:

If-None-Match 请求:

GET / HTTP/1.1
If-None-Match: W/"46897b97f1ec60f7e9234128a807a4da"
Content-Type: application/json; charset=utf-8
Cookie: signin_redirect=http%3A%2F%2Fwww.jianshu.com%2F; _maleskine_session=OTlTVXFmSkFUWkwvckpsL1NQZzRCb3d2WjZKVUd0cjg2bmswNXRCZ2lSYll5NXUwNm9PM3crK1RyVlhLOWdBTGFLYW5uUjJzZ2M5N25OeVMwUWZzNWo3NzBKYnNCaEprN0szMERXc29hYzIvQ3ZLRGgrR21Dc1pGSXYwakt3dytPc3lEdUNqT0VxNm9ObWQzNjd2TllnT1Y1Q2xmeE90OTN5TitiSzgyb1I0ZnVSRzYydko3Yk1Kbk91YmZvYWdEajJBVVZqZnhObkx3TzZxTC9RV3lMVTM2azFsdjVjenU3VElxeFNSV2FsZ2trM3Y0NXV4UW0zSDFVUFp3QzVEbC0tWG5OR0YyWTZ4VzZlZzYrRHZ1ZU9RZz09--f0b6a705d154b0689d3b7dd1c372bacb85bea348
Host: www.jianshu.com
Connection: close
User-Agent: Paw/3.1.4 (Macintosh; OS X/10.12.6) GCDHTTPRequest

If-None-Match 响应 :

HTTP/1.1 304 Not Modified
Date: Tue, 17 Oct 2017 13:06:41 GMT
Server: Tengine
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' *.jianshu.io api.geetest.com static.geetest.com dn-staticdown.qbox.me zz.bdstatic.com *.google-analytics.com hm.baidu.com push.zhanzhang.baidu.com res.wx.qq.com qzonestyle.gtimg.cn as.alipayobjects.com ;style-src 'self' 'unsafe-inline' *.jianshu.io api.geetest.com static.geetest.com ;
ETag: W/"46897b97f1ec60f7e9234128a807a4da"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _maleskine_session=OTlTVXFmSkFUWkwvckpsL1NQZzRCb3d2WjZKVUd0cjg2bmswNXRCZ2lSYll5NXUwNm9PM3crK1RyVlhLOWdBTGFLYW5uUjJzZ2M5N25OeVMwUWZzNWo3NzBKYnNCaEprN0szMERXc29hYzIvQ3ZLRGgrR21Dc1pGSXYwakt3dytPc3lEdUNqT0VxNm9ObWQzNjd2TllnT1Y1Q2xmeE90OTN5TitiSzgyb1I0ZnVSRzYydko3Yk1Kbk91YmZvYWdEajJBVVZqZnhObkx3TzZxTC9RV3lMVTM2azFsdjVjenU3VElxeFNSV2FsZ2trM3Y0NXV4UW0zSDFVUFp3QzVEbC0tWG5OR0YyWTZ4VzZlZzYrRHZ1ZU9RZz09--f0b6a705d154b0689d3b7dd1c372bacb85bea348; path=/; HttpOnly
X-Request-Id: b4e0dee3-4a91-4cc3-84ce-9538ea4d29c5
X-Runtime: 0.037953
X-Via: 1.1 zhouwtong132:2 (Cdn Cache Server V2.0)
Connection: close

可以看出返回码为 304 Not Modified,这时body没有内容。这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。

If-Match 请求:

GET / HTTP/1.1
If-Match: W/"46897b97f1ec60f7e9234128a807a4da"
Content-Type: application/json; charset=utf-8
Cookie: signin_redirect=http%3A%2F%2Fwww.jianshu.com%2F; _maleskine_session=VDUzOFExbmhvUnVLMkhmVDFYQ09vbXJ2Z0EwVXNFQkl1aVg5L0hLb1liR1BnTWhnU2RubFBRTEcwWWZ1TWxabndxTFJ1cTkxQzZVZjYwdXNmM3hOckJoQndVOG81eURUOC9HNHRyZWJ3VnF6NEJXbUo0UW4vR0pqMHRMV0RpaWhLNHhaQWdqc3ZJSnhIWWgzd3hNeDZUOXhFajY4SFdSUnl2UE9LVGVlM3dkekx1WnVXWEZ2Q0NQSWhMMDg0OS91ODQ1NE1kS0NHRGpxTzM2aEZuRDVIUi8xclVYMDg4Q3lNZnF1K3dLcFdPWTB1V0pLNVpqSy8vZVdiWWNZTEQwcS0tMmlZWUdwMjkrZHNhM24vMGJpbzk1UT09--0ef0ba03bd4f031c21a0c3206e640769ccec2cc7
Host: www.jianshu.com
Connection: close
User-Agent: Paw/3.1.4 (Macintosh; OS X/10.12.6) GCDHTTPRequest

If-Match 响应:

HTTP/1.1 200 OK
Date: Tue, 17 Oct 2017 13:08:54 GMT
Server: Tengine
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' *.jianshu.io api.geetest.com static.geetest.com dn-staticdown.qbox.me zz.bdstatic.com *.google-analytics.com hm.baidu.com push.zhanzhang.baidu.com res.wx.qq.com qzonestyle.gtimg.cn as.alipayobjects.com ;style-src 'self' 'unsafe-inline' *.jianshu.io api.geetest.com static.geetest.com ;
ETag: W/"46897b97f1ec60f7e9234128a807a4da"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _maleskine_session=VDUzOFExbmhvUnVLMkhmVDFYQ09vbXJ2Z0EwVXNFQkl1aVg5L0hLb1liR1BnTWhnU2RubFBRTEcwWWZ1TWxabndxTFJ1cTkxQzZVZjYwdXNmM3hOckJoQndVOG81eURUOC9HNHRyZWJ3VnF6NEJXbUo0UW4vR0pqMHRMV0RpaWhLNHhaQWdqc3ZJSnhIWWgzd3hNeDZUOXhFajY4SFdSUnl2UE9LVGVlM3dkekx1WnVXWEZ2Q0NQSWhMMDg0OS91ODQ1NE1kS0NHRGpxTzM2aEZuRDVIUi8xclVYMDg4Q3lNZnF1K3dLcFdPWTB1V0pLNVpqSy8vZVdiWWNZTEQwcS0tMmlZWUdwMjkrZHNhM24vMGJpbzk1UT09--0ef0ba03bd4f031c21a0c3206e640769ccec2cc7; path=/; HttpOnly
X-Request-Id: c9cf0f89-fe3e-46b3-bac4-c7e519660434
X-Runtime: 0.007676
X-Via: 1.1 zhouwtong132:2 (Cdn Cache Server V2.0)
Connection: close

<!DOCTYPE html>
<!--[if IE 6]><html class="ie lt-ie8"><![endif]-->
<!--[if IE 7]><html class="ie lt-ie8"><![endif]-->
<!--[if IE 8]><html class="ie ie8"><![endif]-->
<!--[if IE 9]><html class="ie ie9"><![endif]-->
<!--[if !IE]><!--> <html> <!--<![endif]-->

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-widt
   ...
</html>

表示服务器验证资源的ETags与If-Match的值一致,表示文件没有被修改,则开始继续传送文件,服务器返回 200 OK。

Modified

字段名 说明 用法
Last-Modified 由服务器往客户端发送的 HTTP 头 表示资源的最后修改日期时间
If-Modified-Since 1. 如果文件被修改,就开始传输, 服务器返回 200 OK;2. 如果文件没有被修改,就无需传输, 服务器返回 304 Not Modified. 客户端下载文件时,如果文件没有修改,就不用重新下载
If-Unmodified-Since 1. 如果文件没有被修改,则开始继续传送文件,服务器返回 200 OK;2. 如果文件被修改,则不传输,服务器返回 412 Precondition failed 断点续传,判断文件时候被修改



(待续)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • iOS开发系列--网络开发 概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可...
    lichengjin阅读 3,633评论 2 7
  • 一、概念(载录于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434阅读 8,326评论 6 152
  • 本篇文章篇幅比较长,先来个思维导图预览一下。 一、概述 1.计算机网络体系结构分层 2.TCP/IP 通信传输流 ...
    涤生_Woo阅读 54,921评论 24 557
  • HTTP全称为HyperText Transfer Protocol,从名字不难看出这是一种基于文本的网络协议,对...
    MrPeak阅读 1,445评论 3 21
  • 文/吴伯凡 乔布斯曾经讲过他第一次对父亲痛感失望的经历。在少年乔布斯眼中,身为汽车修理工的父亲拥有一双魔法般的手,...
    大尉说说阅读 464评论 0 0