自定義 iOS soap 請求

提示文章只是用來記錄本人自己在學習過程中所遇到的一些問題的解決方案,如果有什么意見可以留言提出來,不喜勿噴哦!

占位圖
什么是 SOAP 協(xié)議

SOAP 是基于 XML 的簡易協(xié)議,可使應(yīng)用程序在 HTTP 之上進行信息交換。或者更簡單地說:SOAP 是用于訪問網(wǎng)絡(luò)服務(wù)的協(xié)議。如果不了解的 SOAP 的話建議先去了解一下SOAP協(xié)議的結(jié)構(gòu)。

問題提出

在我接手的一個關(guān)于汽車充電樁的項目里,所有的訪問后臺服務(wù)器都是使用的 SOAP 協(xié)議。所以,在最初的時候我采用的是 Git 上面的一個封裝好的也是流傳度很高的 SOAPEngine64(有興趣的同學可以自己去了解了解)。
但是當我們需要對每次請求后臺的 SOAP 請求進行登錄用戶和密碼的驗證的時候出現(xiàn)了問題:要求在 "<soapenv:Header>" 里的 "<AuthHead>" 里面加上自己的用戶名和密碼,以供驗證。但 SOAPEngine64 里面似乎并沒有給我們?nèi)魏蔚慕涌诨蛲獠繀?shù)來添加這部分內(nèi)容。沒有辦法的我只有自定義 SOAP 請求了。

方法構(gòu)思

  • 自己創(chuàng)建一個 SOAP 請求的類,類里定義一個協(xié)議,添加方法用來實現(xiàn)請求的回調(diào)。
  • 創(chuàng)建一個方法用于外部直接調(diào)用 SOAP 請求,傳入相應(yīng)參數(shù)。
  • 接下來就是最關(guān)鍵的 SOAP 協(xié)議的拼接了,這里我把整個 SOAP 協(xié)議分為多段,最后再拼接在一起:
///////////soapMessage 拼接
    NSString *soapHtmlHeadH = [NSString stringWithFormat:
                              @"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
                              "<soapenv:Header>"
                              "<AuthHead xmlns=\"http://tempuri.org/\">"
                               "<loginInfo>"];
    
    NSString *soapHtmlHeadF = [NSString stringWithFormat:
                              @"</loginInfo>"
                              "</AuthHead>"
                              "</soapenv:Header>"
                               "<soapenv:Body>"
                              "<%@ xmlns=\"http://tempuri.org/\">"
                              "<%@>",self.soapMethodName,self.soapParamkey];
    NSString *soapHtmlFoot = [NSString stringWithFormat:
                              @"</%@>"
                              "</%@>"
                             "</soapenv:Body>"
                              "</soapenv:Envelope>",self.soapParamkey,self.soapMethodName];
    
  
    NSString *soapMessage = [NSString stringWithFormat:@"%@%@%@%@%@",soapHtmlHeadH,headerParams,soapHtmlHeadF,params,soapHtmlFoot];
    
    NSLog(@"soapMessage: \n%@",soapMessage);

其中的<loginInfo>就是我想添加進協(xié)議頭部的參數(shù)名,headerParams就是具體的參數(shù)了。

  • 最后就是通過 http 請求發(fā)送請求了。但是注意的地方就是:接收到服務(wù)器的數(shù)據(jù)也是 xml 格式的,我們我們需要一個 XMLReader 類解析一下。

代碼實現(xiàn)

XMLReader類

#import <Foundation/Foundation.h>

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 <NSXMLParserDelegate>

+ (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 <Foundation/Foundation.h>

@protocol APISoapClientDelegate <NSObject>

- (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<APISoapClientDelegate> 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 ()<NSURLConnectionDelegate>


@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:
                              @"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
                              "<soapenv:Header>"
                              "<AuthHead xmlns=\"http://tempuri.org/\">"
                               "<loginInfo>"];
    
    NSString *soapHtmlHeadF = [NSString stringWithFormat:
                              @"</loginInfo>"
                              "</AuthHead>"
                              "</soapenv:Header>"
                               "<soapenv:Body>"
                              "<%@ xmlns=\"http://tempuri.org/\">"
                              "<%@>",self.soapMethodName,self.soapParamkey];
    NSString *soapHtmlFoot = [NSString stringWithFormat:
                              @"</%@>"
                              "</%@>"
                             "</soapenv:Body>"
                              "</soapenv:Envelope>",self.soapParamkey,self.soapMethodName];
    
  
    NSString *soapMessage = [NSString stringWithFormat:@"%@%@%@%@%@",soapHtmlHeadH,headerParams,soapHtmlHeadF,params,soapHtmlFoot];
    
    NSLog(@"soapMessage: \n%@",soapMessage);

    //請求發(fā)送到的路徑
    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];
    
    //如果連接已經(jīng)建好,則初始化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 類型數(shù)據(jù)無需轉(zhuǎn)換字典類型");
                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的過程中需要根據(jù)自己的設(shè)計情況提取 XML 參數(shù)!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 看見你倆的名字合二為一的時候,就是我最快樂的時候。
    米田天空子閱讀 265評論 0 0
  • 「何處?幾夜瀟瀟雨。濕盡檐花,花底人無語。掩屏山,玉爐寒。誰見兩眉愁聚,依闌干?!挂辉~盡,想必熱愛古代詩詞的早已知...
    大唐坐在白日夢上閱讀 404評論 0 1
  • 和先生已經(jīng)分開了快半年了,不得不佩服自己啊,這半年里居然過的還可以??赡苁怯行殞氃谏磉?,自己有空閑時間也去學...
    小縣城生活日記閱讀 1,223評論 9 4

友情鏈接更多精彩內(nèi)容