提示:文章只是用来记录本人自己在学习过程中所遇到的一些问题的解决方案,如果有什么意见可以留言提出来,不喜勿喷哦!
什么是 SOAP 协议
SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。或者更简单地说:SOAP 是用于访问网络服务的协议。如果不了解的 SOAP 的话建议先去了解一下SOAP协议的结构。
问题提出
在我接手的一个关于汽车充电桩的项目里,所有的访问后台服务器都是使用的 SOAP 协议。所以,在最初的时候我采用的是 Git 上面的一个封装好的也是流传度很高的 SOAPEngine64(有兴趣的同学可以自己去了解了解)。
但是当我们需要对每次请求后台的 SOAP 请求进行登录用户和密码的验证的时候出现了问题:要求在 "
方法构思
- 自己创建一个 SOAP 请求的类,类里定义一个协议,添加方法用来实现请求的回调。
- 创建一个方法用于外部直接调用 SOAP 请求,传入相应参数。
- 接下来就是最关键的 SOAP 协议的拼接了,这里我把整个 SOAP 协议分为多段,最后再拼接在一起:
///////////soapMessage 拼接
NSString *soapHtmlHeadH = [NSString stringWithFormat:
@""
""
""
""];
NSString *soapHtmlHeadF = [NSString stringWithFormat:
@" "
" "
" "
""
"<%@ xmlns=\"http://tempuri.org/\">"
"<%@>",self.soapMethodName,self.soapParamkey];
NSString *soapHtmlFoot = [NSString stringWithFormat:
@"%@>"
"%@>"
" "
" ",self.soapParamkey,self.soapMethodName];
NSString *soapMessage = [NSString stringWithFormat:@"%@%@%@%@%@",soapHtmlHeadH,headerParams,soapHtmlHeadF,params,soapHtmlFoot];
NSLog(@"soapMessage: \n%@",soapMessage);
其中的
- 最后就是通过 http 请求发送请求了。但是注意的地方就是:接收到服务器的数据也是 xml 格式的,我们我们需要一个 XMLReader 类解析一下。
代码实现
XMLReader类
#import
enum {
XMLReaderOptionsProcessNamespaces = 1 << 0, // Specifies whether the receiver reports the namespace and the qualified name of an element.
XMLReaderOptionsReportNamespacePrefixes = 1 << 1, // Specifies whether the receiver reports the scope of namespace declarations.
XMLReaderOptionsResolveExternalEntities = 1 << 2, // Specifies whether the receiver reports declarations of external entities.
};
typedef NSUInteger XMLReaderOptions;
@interface XMLReader : NSObject
+ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)errorPointer;
+ (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)errorPointer;
+ (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(XMLReaderOptions)options error:(NSError **)errorPointer;
+ (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(XMLReaderOptions)options error:(NSError **)errorPointer;
@end
#import "XMLReader.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "XMLReader requires ARC support."
#endif
NSString *const kXMLReaderTextNodeKey = @"text";
NSString *const kXMLReaderAttributePrefix = @"@";
@interface XMLReader ()
@property (nonatomic, strong) NSMutableArray *dictionaryStack;
@property (nonatomic, strong) NSMutableString *textInProgress;
@property (nonatomic, strong) NSError *errorPointer;
@end
@implementation XMLReader
#pragma mark - Public methods
+ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)error
{
XMLReader *reader = [[XMLReader alloc] initWithError:error];
NSDictionary *rootDictionary = [reader objectWithData:data options:0];
return rootDictionary;
}
+ (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)error
{
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
return [XMLReader dictionaryForXMLData:data error:error];
}
+ (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(XMLReaderOptions)options error:(NSError **)error
{
XMLReader *reader = [[XMLReader alloc] initWithError:error];
NSDictionary *rootDictionary = [reader objectWithData:data options:options];
return rootDictionary;
}
+ (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(XMLReaderOptions)options error:(NSError **)error
{
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
return [XMLReader dictionaryForXMLData:data options:options error:error];
}
#pragma mark - Parsing
- (id)initWithError:(NSError **)error
{
self = [super init];
if (self)
{
self.errorPointer = *error;
}
return self;
}
- (NSDictionary *)objectWithData:(NSData *)data options:(XMLReaderOptions)options
{
// Clear out any old data
self.dictionaryStack = [[NSMutableArray alloc] init];
self.textInProgress = [[NSMutableString alloc] init];
// Initialize the stack with a fresh dictionary
[self.dictionaryStack addObject:[NSMutableDictionary dictionary]];
// Parse the XML
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setShouldProcessNamespaces:(options & XMLReaderOptionsProcessNamespaces)];
[parser setShouldReportNamespacePrefixes:(options & XMLReaderOptionsReportNamespacePrefixes)];
[parser setShouldResolveExternalEntities:(options & XMLReaderOptionsResolveExternalEntities)];
parser.delegate = self;
BOOL success = [parser parse];
// Return the stack's root dictionary on success
if (success)
{
NSDictionary *resultDict = [self.dictionaryStack objectAtIndex:0];
return resultDict;
}
return nil;
}
#pragma mark - NSXMLParserDelegate methods
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
// Get the dictionary for the current level in the stack
NSMutableDictionary *parentDict = [self.dictionaryStack lastObject];
// Create the child dictionary for the new element, and initilaize it with the attributes
NSMutableDictionary *childDict = [NSMutableDictionary dictionary];
[childDict addEntriesFromDictionary:attributeDict];
// If there's already an item for this key, it means we need to create an array
id existingValue = [parentDict objectForKey:elementName];
if (existingValue)
{
NSMutableArray *array = nil;
if ([existingValue isKindOfClass:[NSMutableArray class]])
{
// The array exists, so use it
array = (NSMutableArray *) existingValue;
}
else
{
// Create an array if it doesn't exist
array = [NSMutableArray array];
[array addObject:existingValue];
// Replace the child dictionary with an array of children dictionaries
[parentDict setObject:array forKey:elementName];
}
// Add the new child dictionary to the array
[array addObject:childDict];
}
else
{
// No existing value, so update the dictionary
[parentDict setObject:childDict forKey:elementName];
}
// Update the stack
[self.dictionaryStack addObject:childDict];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
// Update the parent dict with text info
NSMutableDictionary *dictInProgress = [self.dictionaryStack lastObject];
// Set the text property
if ([self.textInProgress length] > 0)
{
// trim after concatenating
NSString *trimmedString = [self.textInProgress stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[dictInProgress setObject:[trimmedString mutableCopy] forKey:kXMLReaderTextNodeKey];
// Reset the text
self.textInProgress = [[NSMutableString alloc] init];
}
// Pop the current dict
[self.dictionaryStack removeLastObject];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// Build the text value
[self.textInProgress appendString:string];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
// Set the error pointer to the parser's error object
self.errorPointer = parseError;
}
@end
APISoapClient类
#import
@protocol APISoapClientDelegate
- (void)didReceiveData:(NSData *)data;
- (void)didFinishLoadingWithResult:(id)result isDictionary:(BOOL)isDictionary;
- (void)didFailWithError:(NSError *)error;
- (void)FailWithXML:(NSNumber *)code isDictionary:(BOOL)isDictionary errorDescription:(NSString *)errorDescription;
@end
typedef void (^ResultBlock) (id result,BOOL isDictionary,id message);
@interface APISoapClient : NSObject
{
NSMutableData *_webData;
}
@property (nonatomic,copy) ResultBlock resultBlock;
@property (nonatomic,assign) NSInteger statusCode;
@property (nonatomic, copy) NSString *soapMethodName;
@property (nonatomic, copy) NSString *soapParamkey;
@property (nonatomic,assign) id delegate;
- (NSURLConnection *)getDataAPIResultWithURL:(NSString *)url
headerParams:(NSString *)headerParams
params:(NSString *)params
htttpMethod:(NSString *)htttpMethod//POST
withSOAPAction:(NSString *)soapAction
withMethodName:(NSString *)soapMethodName
withParamKey:(NSString *)soapParamkey
withResultBlock:(ResultBlock)resultBock;
@end
#import "APISoapClient.h"
#import "XMLReader.h"
#import "DictionaryWithJsonString.h"
@interface APISoapClient ()
@end
@implementation APISoapClient
- (NSURLConnection *)getDataAPIResultWithURL:(NSString *)url
headerParams:(NSString *)headerParams
params:(NSString *)params
htttpMethod:(NSString *)htttpMethod
withSOAPAction:(NSString *)soapAction
withMethodName:(NSString *)soapMethodName
withParamKey:(NSString *)soapParamkey
withResultBlock:(ResultBlock)resultBock
{
self.soapMethodName = soapMethodName;
self.soapParamkey = soapParamkey;
self.resultBlock = resultBock;
///////////soapMessage 拼接
NSString *soapHtmlHeadH = [NSString stringWithFormat:
@""
""
""
""];
NSString *soapHtmlHeadF = [NSString stringWithFormat:
@" "
" "
" "
""
"<%@ xmlns=\"http://tempuri.org/\">"
"<%@>",self.soapMethodName,self.soapParamkey];
NSString *soapHtmlFoot = [NSString stringWithFormat:
@"%@>"
"%@>"
" "
" ",self.soapParamkey,self.soapMethodName];
NSString *soapMessage = [NSString stringWithFormat:@"%@%@%@%@%@",soapHtmlHeadH,headerParams,soapHtmlHeadF,params,soapHtmlFoot];
NSLog(@"soapMessage: \n%@",soapMessage);
//请求发送到的路径
NSURL *path = [NSURL URLWithString:url];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:path cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:180.0];
NSString *msgLength = [NSString stringWithFormat:@"%lu", (unsigned long)[soapMessage length]];
[theRequest addValue:msgLength forHTTPHeaderField:@"Content-Length"];
[theRequest addValue:@"text/xml;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[theRequest addValue:soapAction forHTTPHeaderField:@"SOAPAction"];
[theRequest setHTTPMethod:htttpMethod];
[theRequest setHTTPBody:[soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
/////////////////
//请求 iOS9.0后NSURLSession代替
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
//如果连接已经建好,则初始化data
if( theConnection )
{
_webData = [NSMutableData data];
}
else
{
NSLog(@"theConnection is NULL");
}
return theConnection;
}
/**
* Description soap xml 拼接(Dic To xml)
*
* @param soapHtmlMiddle soapHtmlMiddle description
* @param params params description
*/
-(void)appendSoapHtmlMiddle:(NSMutableString **)soapHtmlMiddle WithParams:(NSMutableDictionary *)params
{
for (NSString *key in params.allKeys) {
[*soapHtmlMiddle appendString:[NSString stringWithFormat:@"<%@>",key]];
id value = params[key];
if ([value isKindOfClass:[NSDictionary class]]) {
[self appendSoapHtmlMiddle:&*soapHtmlMiddle WithParams:value];
}else if ([value isKindOfClass:[NSArray class]]){
for (id oneValue in value) {
if ([oneValue isKindOfClass:[NSDictionary class]]) {
[self appendSoapHtmlMiddle:&*soapHtmlMiddle WithParams:oneValue];
}
}
}else{
[*soapHtmlMiddle appendString:[NSString stringWithFormat:@"%@",value]];
}
[*soapHtmlMiddle appendString:[NSString stringWithFormat:@"%@>",key]];
}
}
/*
*/
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// [webData setLength: 0];
self.statusCode = [(NSHTTPURLResponse*)response statusCode];
// NSLog(@"connection: didReceiveResponse:1------%@,statusCode:%ld",response,self.statusCode);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_webData appendData:data];
[self.delegate didReceiveData:data];
// NSLog(@"connection: didReceiveData:2");
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"ERROR with theConenction----:%@",error);
// if (self.resultBlock) {
// NSString *message = error.description;
// self.resultBlock(NULL,NO,message);
// }
[self.delegate didFailWithError:error];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// NSLog(@"3 DONE. Received Bytes: %lu", (unsigned long)[_webData length]);
NSString *st = [[NSString alloc] initWithData:_webData encoding:NSUTF8StringEncoding];
// NSLog(@"connectionDidFinishLoading----:%@",st);
//拼接key
NSString * responseKey = [NSString stringWithFormat:@"%@"@"%@",_soapMethodName,@"Response"];
NSString * resultKey = [NSString stringWithFormat:@"%@"@"%@", _soapMethodName, @"Result"];
NSLog(@"得到的XML=%@", st);
NSError *error;
id resultDic = [XMLReader dictionaryForXMLString:st error:&error];
// NSLog(@"result---:%@",resultDic);
BOOL isDic = [resultDic isKindOfClass:[NSDictionary class]];
id message;
if (!error) {
if (isDic) {
resultDic = resultDic[@"soap:Envelope"][@"soap:Body"][responseKey][resultKey][@"text"];
id resultDicTemp = [DictionaryWithJsonString dictionaryWithJsonString:resultDic];
if(resultDicTemp == nil){//string 类型
NSLog(@"Sting 类型数据无需转换字典类型");
isDic = false;
}else{ //字典类型
resultDic = resultDicTemp;
message = resultDic[@"Value"];
}
}else{
resultDic = [NSNumber numberWithInteger:110];
}
NSLog(@"%@ request end",self.soapMethodName);
// if (self.resultBlock) {
// self.resultBlock(resultDic,isDic,message);
// }
if([_delegate respondsToSelector:@selector(didFinishLoadingWithResult:isDictionary:)]){
[_delegate didFinishLoadingWithResult:resultDic isDictionary:isDic];
}else{
NSLog(@"NoSuchMethod");
}
}else {
NSLog(@"解析错误%@",error);
// if (self.resultBlock) {
// self.resultBlock([NSNumber numberWithInteger:110],isDic,error.description);
// }
[self.delegate FailWithXML:[NSNumber numberWithInteger:110] isDictionary:isDic errorDescription:error.description];
};
}
@end
注意:在解析xml的过程中需要根据自己的设计情况提取 XML 参数!