进入这家公司将近一年的时间了. 从进入公司的开始就一直对老项目遗留的网络请求方式心存芥蒂, 之前因为各种各样的原因一直没有去动它. 一直到前几天突然想起来这事情好像可以搞一搞, 遂写一篇文章记录一下, 顺便分享一些小东西.
在继续往下之前你需要先去了解一些概念:SOAP、WSDL、CXF,和他们之间的关系。这里我觉得这个帖子比较好,推荐一下,Web Service笔记(三):wsdl 与 soap协议详解 , 对XML或者HTML稍微有点了解看了这篇文章之后对WSDL基本都能大体了解了,这里也感谢一下作者。
假定现在你对它们有个大体的了解,SOAP请求就是你发一段XML给后台,然后后台返回数据给你,它是通用的,参数后台会在XML中提取,所以我们在这个过程中其实就是在于传的XML的内容,本文也会讲到中间遇到的一些细节。
不要把soap想得太复杂, 这种方式其实和普通的get/post差不多, 方式正确了也许会更加简单.
首先XML内容,你的SOAP协议版本要和后台一致,不然后台报错会说版本不一致之类的,这里以1.1版本为范例, 1.2同理.
● 以下为soap1.1的请求和响应示范
//请求体
c9c72404-da88-4623-b724-ca1d7359a101
102
2D2097539F72EDF27A8AD78E20017955
中间的Guid,appCode,appSign标签以及其间的内容就是我在这次请求中给后台的参数.有几个键值对也其实就是相当于几个参数
下面是响应体.
//响应体
{"Success":"1","CurUserGuid":"e3d14419-77ac-4756-8e79-bf45d06fd2b7","Isleader":"1","Name":"局机关管理员","CompName":"局机
关","CompID":"1","DeptName":"","DutyName":""}
到了这里就很明了了, 我们要做的就是拿到
好了,要传什么,注意点都讲了,现在到了客户端的问题,用官方SDK请求是不是感觉很麻烦?是的,对于用惯了AFN或者自己封装的网络请求工具类的人来说如果每次都要写这多么代码发一次请求太痛苦了,于是我想可不可以用AFN请求SOAP,一开始想用manager发请求,直接把XML当params发POST肯定是直接挂了,于是想要设置HTTPBody要不用AFHTTPRequestOperation?没错这样确实可以,代码如下:
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[soapStr dataUsingEncoding:NSUTF8StringEncoding]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
// 设置返回数据格式
operation.responseSerializer = [AFHTTPResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
NSString *result = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(@"AFN--成功--结果:%@----返回数据%@", result, responseObject);
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
NSLog(@"AFN--失败--%@", error.localizedDescription);
}];
[operation start];
这一看感觉和NSURLSession没多大差别,还是想用manager,关键问题就在于设置request的HTTBbody为XML,但是AFHTTPSessionManager已经把request封装了,默认用的params,怎么改?于是想改动或者添加AFN内部方法,但是总感觉这样不好,万一更新库了又要搞一遍。于是想能否拦截这个request,或者通过manager.requestSerializer设置HTTPBody,敲set浏览一下没有HTTPBody字眼的,用KVC也不行,那样还是相当于把XML当params传了,伤心绝望之时看到这个方法
[manager.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) {
}]
一看里面有request 有 params 高兴了,说不定在这里能拦截,于是直接写
[manager.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) {
return soapStr;
}]
请求成功,高兴了。
接下来另一个问题了,简单封装一下,我收到的是二进制,所以把AFN封装成了个工具类,供大家参考,欢迎提出改进
#import "ZTLNetworking.h"
@implementation NSDictionary (Convert)
- (NSString *)dictToJson{
NSData *dictData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted error:nil];
return [[NSString alloc]initWithData:dictData encoding:NSUTF8StringEncoding];
}
@end
@interface ZTLNetworking()
@end
@implementation ZTLNetworking
#pragma mark - Public
/** soap拼接*/
+ (nullable NSMutableString *)soapInvokeWithParams:(NSMutableArray *)params method:(NSString *)method{
NSMutableString * post = [[ NSMutableString alloc ] init] ;
[ post appendString:
@"\n"
"\n"
"\n" ];
[ post appendString:@"<"];
[ post appendString:method];
[ post appendString:[NSString stringWithFormat:@" xmlns=\"http://tempuri.org/\">\n"]]; //根据需求自行更换
for (NSDictionary *dict in params)
{
NSString *value = nil;
NSString *key = [[dict keyEnumerator] nextObject];
if (key != nil)
{
value = [dict valueForKey:key];
[ post appendString:@"<"];
[ post appendString:key];
[ post appendString:@">"];
if( value != nil )
{
[ post appendString:value];
}
else
{
[ post appendString:@""];
}
[ post appendString:@""];
[ post appendString:key];
[ post appendString:@">\n"];
}
}
[ post appendString:@""];
[ post appendString:method];
[ post appendString:@">\n"];
[ post appendString:
@" \n"
" \n"
];
return post;
}
+ (void)soapBody:(NSMutableArray *)soapBody
method:(NSString *)method
success:(void (^)(id responseObject, id jsonStr))success
failure:(void(^)(NSError *error))failure {
NSString *soapUrl = @"http://172.16.11.45:8204/Service/OAMobileService.asmx";
NSDictionary *appsign = @{@"appSign": appSign};
NSDictionary *appcode = @{@"appCode": appCode};
[soapBody addObject:appcode];
[soapBody addObject:appsign];
NSString *soapStr = [ZTLNetworking soapInvokeWithParams:soapBody method:method];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 设置请求超时时间
manager.requestSerializer.timeoutInterval = 30;
// 返回NSData
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// 设置请求头,也可以不设置
[manager.requestSerializer setValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[manager.requestSerializer setValue:[NSString stringWithFormat:@"%zd", soapStr.length] forHTTPHeaderField:@"Content-Length"];
// 设置HTTPBody
[manager.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) {
return soapStr;
}];
[manager POST:soapUrl parameters:soapStr success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
// 把返回的二进制数据转为字符串
NSString *result = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
// 利用正则表达式取出 之间的字符串
NSString *pattern = [NSString stringWithFormat:@"(?<=%@Result\\>).*(?=%@Result)",method,method];
NSRegularExpression *regular = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSDictionary *dict = [NSDictionary dictionary];
for (NSTextCheckingResult *checkingResult in [regular matchesInString:result options:0 range:NSMakeRange(0, result.length)]) {
// 得到字典
dict = [NSJSONSerialization JSONObjectWithData:[[result substringWithRange:checkingResult.range] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableLeaves error:nil];
}
// 请求成功并且结果有值把结果传出去, 传Json出去的原因主要是因为个人习惯了用网页查看json, 不然觉得麻烦
if (success && dict) {
success(dict, [dict dictToJson]);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(error);
}
}];
}
@end