iOS用AFN做Soap请求webService

  进入这家公司将近一年的时间了. 从进入公司的开始就一直对老项目遗留的网络请求方式心存芥蒂, 之前因为各种各样的原因一直没有去动它. 一直到前几天突然想起来这事情好像可以搞一搞, 遂写一篇文章记录一下, 顺便分享一些小东西.

   在继续往下之前你需要先去了解一些概念: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的请求和响应示范

//请求体
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<GetCLYYInfoList xmlns="http://tempuri.org/">
<Guid>c9c72404-da88-4623-b724-ca1d7359a101</Guid>
<appCode>102</appCode>
<appSign>2D2097539F72EDF27A8AD78E20017955</appSign>
</GetCLYYInfoList>
</soap:Body>
</soap:Envelope>

<soap: Body>标签以外的不用改,head一般也不用传,要调用的方法和参数都包在body里面,比如这个例子中,GetCLYYInfoList是WSDL文档发布的你要调用的方法名,其后接的xmlns是你wsdl文档中对应的targetNameSpace. 这些可以向你们的后台索取.
中间的Guid,appCode,appSign标签以及其间的内容就是我在这次请求中给后台的参数.有几个键值对也其实就是相当于几个参数
下面是响应体.

//响应体
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body>
<LoginResponse xmlns="http://tempuri.org/">
<LoginResult>
{"Success":"1","CurUserGuid":"e3d14419-77ac-4756-8e79-bf45d06fd2b7","Isleader":"1","Name":"局机关管理员","CompName":"局机
关","CompID":"1","DeptName":"","DutyName":""}
</LoginResult>
</LoginResponse>
</soap:Body>
</soap:Envelope>

到了这里就很明了了, 我们要做的就是拿到<LoginResult>标签里的内容.

好了,要传什么,注意点都讲了,现在到了客户端的问题,用官方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:
     @"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
     "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
     " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
     " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
     "<soap:Body>\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:
     @"</soap:Body>\n"
     "</soap:Envelope>\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];
        
        // 利用正则表达式取出<return></return>之间的字符串
        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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容