1 概述
我之前一直做android的漆撞,后來也做過ios,也寫過REACT-NATIVE。
以前一直沒有什么寫文章習慣于宙,但發(fā)現(xiàn)浮驳,好多知識容易忘記,還是決定記錄下來吧捞魁,不然很多知識找起來麻煩抹恳。也希望能跟廣大同行們交流經驗,互相學習.如果有寫的不好署驻,有錯誤的地方奋献,歡迎指正哈健霹!說了一堆廢話,下面進入正題.
2 POST請求的常用格式
HTTP/1.1協(xié)議規(guī)定的HTTP請求方法有OPTIONS瓶蚂、GET糖埋、HEAD、POST窃这、PUT瞳别、DELETE、TRACE杭攻、CONNECT 這幾種祟敛。其中POST一般用來向服務端提交數據,接下來要討論POST提交數據的幾種方式兆解。協(xié)議規(guī)定POST提交的數據必須放在消息主體中馆铁,但協(xié)議并沒有規(guī)定數據必須使用什么編碼方式。
實際上锅睛,開發(fā)者完全可以自己決定消息主體的格式埠巨,只要最后發(fā)送的 HTTP 請求滿足上面的格式就可以。
但是现拒,數據發(fā)送出去辣垒,還要服務端解析成功才有意義。一般服務端語言如java印蔬、php勋桶、python等,以及它們的framework侥猬,都內置了自動解析常見數據格式的功能例驹。服務端通常是根據請求頭(headers)中的Content-Type字段來獲知請求中的消息主體是用何種方式編碼,再對主體進行解析陵究。所以說到POST提交數據方案,包含了Content-Type和消息主體編碼方式兩部分奥帘。
2.1 application/x-www-form-urlencoded格式的POST請求
這應該是最常見的 POST 提交數據的方式了铜邮。瀏覽器的原生表單,如果不設置enctype屬性寨蹋,那么最終就會以application/x-www-form-urlencoded方式提交數據松蒜。Content-Type被指定為application/x-www-form-urlencoded,提交的數據按照 key1=val1&key2=val2的方式進行編碼已旧,key和val都進行了URL轉碼秸苗。
下面這個請求是簡書進入一篇文章頁面的時候,會自動往服務器POST一個請求运褪,估計是統(tǒng)計文章被閱讀的次數等功能惊楼。具體看下面:
//發(fā)送的請求玖瘸,刪除了cookie相關的部分
POST /notes/e15592ce40ae/mark_viewed.json HTTP/1.1
Host: www.reibang.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
X-CSRF-Token: vJvptva4Tqou/V3dd3nFCrcvRsb78FReHuIYZke5PVAnfR/tIAAMCfuaB2Z2/gaEohIZAsiEksUYyPqzg3DpSA==
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://www.reibang.com/p/e15592ce40ae
Content-Length: 98
Connection: keep-alive
Cache-Control: max-age=0
//請求體
uuid=4e3abc0f-1824-4a5d-982f-7d9dee92d9cd&referrer=http%3A%2F%2Fwww.reibang.com%2Fu%2Fad726ba6935d
用AFHTTPSessionManager實現(xiàn)上面這個application/x-www-form-urlencoded請求。
//以下是實際代碼
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSDictionary *params = @{
@"uuid":@"4e3abc0f-1824-4a5d-982f-7d9dee92d9cd",
@"referrer":@"http://www.reibang.com/p/e15592ce40ae"
};
NSURLSessionDataTask *task = [manager POST:@"http://www.reibang.com//notes/e15592ce40ae/mark_viewed.json" parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
NSLog(@"進度更新");
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"返回數據:%@",responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"返回錯誤:%@",error);
}];
[task resume];
2.2 multipart/form-data格式的POST請求
Multipart/form-data的基礎方法是POST , 也就是說是由POST方法來組合實現(xiàn)的. Multipart/form-data與POST方法的不同之處在于請求頭和請求體. Multipart/form-data的請求頭必須包含一個特殊的頭信息 : Content-Type , 且其值也必須規(guī)定為multipart/form-data , 同時還需要規(guī)定一個內容分割符用于分割請求體中的多個POST的內容 , 如文件內容和文本內容自然需要分割開來 , 不然接收方就無法正常解析和還原這個文件了. Multipart/form-data的請求體也是一個字符串 , 不過和post的請求體不同的是它的構造方式 , post是簡單的name=value值連接 , 而Multipart/form-data則是添加了分隔符等內容的構造體.
請求的頭部信息如下:
//其中xxxxx是我自定義的分隔符檀咙,每個人都可以選擇自己的分隔符
Content-Type: multipart/form-data; boundary=xxxxx
下面我們來看一下一個我的Multipart/form-data請求體:
POST /uploadFile HTTP/1.1
Host: 這里是url,就不暴露了_
Content-Type: multipart/form-data; boundary=xxxxx
Connection: keep-alive
Accept: /
User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0
Content-Length: 32175
Accept-Language: en-us
Accept-Encoding: gzip, deflate
--xxxxx
Content-Disposition: form-data;name="file"
img.jpeg
--xxxxx
Content-Disposition: form-data;name="businessType"
CC_USER_CENTER
--xxxxx
Content-Disposition: form-data;name="fileType"
image
--xxxxx
Content-Disposition:form-data;name="file";filename="img1.jpeg"
Content-Type:image/png
這里是圖片數據,太長了.我就刪了
--xxxxx--
這個請求有三個參數file,businessType,fileType雅倒。比如file參數和他的值就通過如下格式傳輸:
--xxxxx
Content-Disposition: form-data;name="file"
img.jpeg
上面這種就是一個參數與之對應的值。協(xié)議規(guī)定的就是這個格式弧可,沒有為什么蔑匣。我們可以看看圖片數據部分:
--xxxxx
Content-Disposition:form-data;name="file";filename="img1.jpeg"
Content-Type:image/png
這里是圖片數據,太長了.我就刪了
--xxxxx--
其中name="參數名" filename="文件名" 其中參數名這個要和接收方那邊相對應 正常開發(fā)中可以去問服務器那邊 , 文件名是說在服務器端保存成文件的名字 , 這個參數然并卵 , 因為一般服務端會按照他們自己的要求去處理文件的存儲.
下一行是指定類型 , 我這里示例中寫的是PNG圖片類型 , 這個可以根據你的實際需求的寫。如果我們要上傳多分圖片或者文件棕诵,則只需要按照指定格式就可以了裁良,比如下面就是上傳兩張圖片的請求:
POST /uploadFile HTTP/1.1
Host: 這里是url,就不暴露了_
Content-Type: multipart/form-data; boundary=xxxxx
Connection: keep-alive
Accept: /
User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0
Content-Length: 32175
Accept-Language: en-us
Accept-Encoding: gzip, deflate
--xxxxx
Content-Disposition: form-data;name="file"
img.jpeg
--xxxxx
Content-Disposition: form-data;name="businessType"
CC_USER_CENTER
--xxxxx
Content-Disposition: form-data;name="fileType"
image
--xxxxx
Content-Disposition:form-data;name="file";filename="img1.jpeg"
Content-Type:image/png
這里是圖片1數據,太長了.我就刪了
--xxxxx
Content-Disposition:form-data;name="file";filename="img2.jpeg"
Content-Type:image/png
這里是圖片1數據,太長了.我就刪了
--xxxxx--
下面是我Demo中一個multipart/form-data請求的實現(xiàn)代碼,分別用NSRULDataTask和AFHTTPSessionManager實現(xiàn),我們可以發(fā)現(xiàn)用第二種方法簡便了很多,因為AFN已經幫我們做好了拼接工作:
//方法一
- (IBAction)updatePic:(id)sender {
//請求頭參數
NSDictionary *dic = @{
@"businessType":@"CC_USER_CENTER",
@"fileType":@"image",
@"file":@"img.jpeg"
};
//請求體圖片數據
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]);
//創(chuàng)建request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
//post方法
[request setHTTPMethod:@"POST"];
// 設置請求頭格式為Content-Type:multipart/form-data; boundary=xxxxx
//[request setValue:@"multipart/form-data; boundary=xxxxx" forHTTPHeaderField:@"Content-Type"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
//請求體里面的參數
NSDictionary *bodyDic = @{
@"Content-Disposition":@"form-data;name=\"file\";filename=\"img.jpeg\"",
@"Content-Type":@"image/png",
};
[formData appendPartWithHeaders:bodyDic body:imageData];
} progress:^(NSProgress * _Nonnull uploadProgress) {
NSLog(@"下載進度");
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"下載成功:%@",responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"下載失敗%@",error);
}];
[task resume];
}
//方法二
- (IBAction)multipartformPost2:(id)sender {
//參數
NSDictionary *dic = @{
@"businessType":@"CC_USER_CENTER",
@"fileType":@"image",
@"file":@"img.jpeg"
};
NSString *boundaryString = @"xxxxx";
NSMutableString *str = [NSMutableString string];
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[str appendFormat:@"--%@\r\n",boundaryString];
[str appendFormat:@"%@name=\"%@\"\r\n\r\n",@"Content-Disposition: form-data;",key];
[str appendFormat:@"%@\r\n",obj];
}];
NSMutableData *requestMutableData=[NSMutableData data];
[str appendFormat:@"--%@\r\n",boundaryString];
[str appendFormat:@"%@:%@",@"Content-Disposition",@"form-data;"];
[str appendFormat:@"%@=\"%@\";",@"name",@"file"];
[str appendFormat:@"%@=\"%@\"\r\n",@"filename",@"img1.jpeg"];
[str appendFormat:@"%@:%@\r\n\r\n",@"Content-Type",@"image/png"];
//轉換成為二進制數據
[requestMutableData appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]);
//文件數據部分
[requestMutableData appendData:imageData];
//添加結尾boundary
[requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundaryString] dataUsingEncoding:NSUTF8StringEncoding]];
//創(chuàng)建一個請求對象
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
//post方法
[request setHTTPMethod:@"POST"];
// 設置請求頭格式為Content-Type:multipart/form-data; boundary=xxxxx
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundaryString] forHTTPHeaderField:@"Content-Type"];
//session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [session uploadTaskWithRequest:request fromData:requestMutableData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);
}];
[task resume];
}
Multipart/form-data格式的POST請求總結:
文件類型參數中name="參數名"一定要和服務端對應, 開發(fā)的時候 , 可以問服務端人員校套,我這里是file价脾。
上傳文件的數據部分使用二進制數據(NSData)拼接。
上邊界部分和下邊界部分的字符串 , 最后都要轉換成二進制數據(NSData) , 和文件部分的二進制數據拼接在一起 , 作為請求體發(fā)送給服務器搔确。
每一行末尾需要有一定的`\r\n·彼棍。
2.3 application/json格式的POST請求
接下來我將常使用NSURLSessionDataTask做一個application/json的POST請求。并且請求體數據我存儲在一個test.txt文件中膳算,從文件中讀取出來然后上傳座硕。
//test.txt文件內容
{"name":"尋歡","phone":"187***"}
通過抓包軟件我的請求如下,和其他POST請求原理一樣涕蜂,只是拼接請求體的方式不一樣华匾,并且更具不同格式的請求體,設置不同的Content-Type:
POST /posts HTTP/1.1
Host: jsonplaceholder.typicode.com
Content-Type: application/json
Connection: keep-alive
Accept: application/json
User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0
Content-Length: 31
Accept-Language: en-us
Accept-Encoding: gzip, deflate
{"name":"尋歡","phone":"187***"}
下面是我Demo的具體實現(xiàn)
- (IBAction)applicationjsonPOST2:(id)sender {
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://jsonplaceholder.typicode.com/posts"]];
//指請求體的類型机隙。由于我們test.txt里面的文件是json格式的字符串蜘拉。所以我這里指定為`application/json`
[request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setHTTPMethod:@"POST"];
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setTimeoutInterval:20];
NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"txt"];
NSURL *url = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
//使用Block來處理返回數據
NSURLSessionDataTask *task = [session uploadTaskWithRequest:request fromFile:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);
}];
[task resume];
}
3 總結
當然conent-type 還有別的application/xml,application/javascript,text/plain 有鹿,這個請求頭的屬性旭旭,至始至終都提供了讓我們自定義請求體的格式,當然需要服務器配合.不過我們這些做前段的通常都是配合服務器的哈哈葱跋。不過持寄,現(xiàn)在的框架基本都為我們做了這些請求體格式的拼接工作.