• 够快网盘支持与iOS-ASIHTTPRequest框架学习


    够快网盘支持与iOS-ASIHTTPRequest框架学习

             前段时间在公司的产品中支持了够快网盘,用于云盘存储。

             在这个过程中,学习到了很多新的知识,也遇到了很多问题,在此记录一下。

             首先就够快的API总结一下。

             一、请求参数中的签名。第一点是生成字符串,例如”2 3 1”,在C#中是不需要加上@前置符号的,这是我一个同事犯过的错误。第二点是签名算法,按照原文提示:将生成的字符利用client_secret作为key进行hmac-sha1加密,然后再进行base64 encode,最后对结果进行rfc3986 URL编码,即:encodeURI(base64_encode(hmac-sha1([string], [client_secret])))。OC的代码如下:

    - (NSString*)getRequestSign:(NSString*)string
    {
        const char *cString = [string cStringUsingEncoding:NSUTF8StringEncoding];
        const char *cSecret = [kCHRISGoKuaiClientSecret cStringUsingEncoding:NSUTF8StringEncoding];
        
        char cHMAC[CC_SHA1_DIGEST_LENGTH];
        CCHmac(kCCHmacAlgSHA1, cSecret, strlen(cSecret), cString, strlen(cString), cHMAC);
        
        NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:CC_SHA1_DIGEST_LENGTH];
        NSString *hash = [HMAC base64Encoding];
        [HMAC release];
        
        NSString *sign = (NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)hash, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
        return sign;
    }

             在此,需要先添加头文件引用:

    #import <CommonCrypto/CommonHMAC.h>和#import <CommonCrypto/CommonCryptor.h>

             二、删除和上传api中的路径参数。对象若为文件,则api中路径参数需要去掉首尾的”/”符号;对象若为文件夹,则api中需要在上述基础上,再在末尾加上一个”/”符号。

             保存access_token

    在得到用户的access_token、expires_in、refresh_token以后,可以保存到设备端,避免繁琐的登陆授权操作。采用NSUserDefaults是不安全的,证书和密码之类的私密信息需要更为安全的keychain来保存。封装的keychain如下:

    #import <Foundation/Foundation.h>
    
    @interface CHRISKeyChain : NSObject
    
    @property (readwrite, nonatomic, retain) NSDictionary* keyChainDic;
    
    + (id)keyChainForId:(NSString*)keyChainId;
    
    - (NSDictionary *)credentials;
    
    - (void)setCredentials:(NSDictionary *)credentials;
    
    - (void)deleteCredentials;
    @end
    #import "CHRISKeyChain.h"
    
    static NSMutableDictionary* g_keyChainDic = nil;
    
    @implementation CHRISKeyChain
    @synthesize keyChainDic = _keyChainDic;
    
    + (id)keyChainForId:(NSString*)keyChainId
    {
        if (!g_keyChainDic)
        {
            g_keyChainDic = [[NSMutableDictionary dictionary] retain];
        }
        CHRISKeyChain* keyChainObject = [g_keyChainDic objectForKey:keyChainId];
        if (!keyChainObject)
        {
            NSDictionary* keyChainDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                          (id)kSecClassGenericPassword, (id)kSecClass,
                                          keyChainId, (id)kSecAttrService, nil];
            keyChainObject = [[[CHRISKeyChain alloc] init] autorelease];
            keyChainObject.keyChainDic = keyChainDict;
            [g_keyChainDic setObject:keyChainObject forKey:keyChainId];
            return keyChainObject;
        }
        return keyChainObject;
    }
    
    - (void)dealloc
    {
        self.keyChainDic = nil;
        [super dealloc];
    }
    
    - (NSDictionary *)credentials
    {
        NSMutableDictionary *searchDict = [NSMutableDictionary dictionaryWithDictionary:_keyChainDic];
        [searchDict setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        [searchDict setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
        [searchDict setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
        
        NSDictionary *attrDict = nil;
        OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDict, (CFTypeRef *)&attrDict);
        [attrDict autorelease];
        NSData *foundValue = [attrDict objectForKey:(id)kSecValueData];
        
        if (status == noErr && foundValue)
        {
            return [NSKeyedUnarchiver unarchiveObjectWithData:foundValue];
        }
        if (status != errSecItemNotFound)
        {
            NSLog(@"error reading stored credentials (%ld)", status);
        }
        return nil;
    }
    
    - (void)setCredentials:(NSDictionary *)credentials
    {
        NSData *credentialData = [NSKeyedArchiver archivedDataWithRootObject:credentials];
        
        NSMutableDictionary *attrDict = [NSMutableDictionary dictionaryWithDictionary:_keyChainDic];
        [attrDict setObject:credentialData forKey:(id)kSecValueData];
        
        NSArray *version = [[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."];
        if ([[version objectAtIndex:0] intValue] >= 4) {
            [attrDict setObject:(id)kSecAttrAccessibleWhenUnlocked forKey:(id)kSecAttrAccessible];
        }
        
        OSStatus status = noErr;
        
        if ([self credentials])
        {
            [attrDict removeObjectForKey:(id)kSecClass];
            status = SecItemUpdate((CFDictionaryRef)_keyChainDic, (CFDictionaryRef)attrDict);
        }
        else
        {
            status = SecItemAdd((CFDictionaryRef)attrDict, NULL);
        }
        
        if (status != noErr)
        {
            NSLog(@"error saving credentials (%ld)", status);
        }
    }
    
    - (void)deleteCredentials
    {
        OSStatus status = SecItemDelete((CFDictionaryRef)_keyChainDic);
        
        if (status != noErr)
        {
            NSLog(@"error deleting credentials (%ld)", status);
        }
    }
    
    @end

             够快网盘之ASIHTTPRequest框架。

             既然是网盘,必然涉及文件上传和下载等网络请求,这里采用了ASIHTTPRequest框架来实现网络请求和文件的上传、下载。

             一、够快网盘的下载api采用get方式:

    ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL: url];
    [request setCompletionBlock:^{
        NSDictionary* params = [request.responseString objectFromJSONString];
    ....
    }];
    [request setFailedBlock:^{…}];
    [request setStartedBlock:^{…}];
    [request setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
        Meta.synProgress =( (double)request.totalBytesRead + request.partialDownloadSize) / total;
    }];
    [request setDownloadDestinationPath: filePath];
    [request setAllowCompressedResponse: NO];
    [request setAllowResumeForFileDownloads: YES];
    [request setShouldPresentCredentialsBeforeChallenge: YES];
    [request setShowAccurateProress: YES];
    [request startAsynchronous];

    说明:url为NSURL对象;filePath为本地存储路径;BytesReceivedBlock可以用来显示当前下载进度的百分比。objectFromJSONString方法需要先添加JSONKit并引用头文件。

             二、够快网盘的上传api步骤2采用了post multipart/form-data方式:

    ASIFormDataRequest* request = [ASIHTTPRequest requestWithURL: url];
    [request setPostValue: … forKey: …];
    …..
    [request setPostValue: @”file” forKey: @”filefield”];
    request.requestMethod = @”POST”;
    [request addRequestHeader:@”Content-Type” value:@”Multipart/form-data”]
    [request setCompletionBlock:^{
        NSDictionary* params = [request.responseString objectFromJSONString];
    …..
    }];
    [request setFailedBlock:^{….}];
    [request setStartedBlock:^{…..}];
    [request setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
        Meta.synProgress =( (double)request.totalBytesSent )/ total;
    }];
    [request setDownloadDestinationPath: filePath];
    [request setAllowCompressedResponse: NO];
    [request setAllowResumeForFileDownloads: YES];
    [request setShouldPresentCredentialsBeforeChallenge: YES];
    [request setShowAccurateProress: YES];
    [request setShouldStreamPostFromDisk: YES];
    [request setFile: localPath withFileName: [meta.path lastPathComponent] andContentType: meta.contentType forKey: @”file”];
    [request startAsynchronous];

    说明:[request setPostValue: @”file” forKey: @”filefield”];中的value必须和后面的forKey一致,这也符合api最后一个参数的说明。

             当然还有其他方面,比如之前删除和上传没有成功,但是状态码返回200,经过与够快的开发人员一番沟通,发现了这两个api的缺陷,现在已经完善了,不会再遇到。

             通过对够快网盘的支持,我也进一步学习了iOS的网络编程,收获还是很多的。

  • 相关阅读:
    Table Scan, Index Scan, Index Seek
    Ndo v3.1发布了!
    手动建立强类型DataSet
    <.NET分布式应用程序开发>读书笔记 第十章:Q&A
    HQL语法大全
    IT人才最容易犯的几个错误
    在线查询Windows API
    ODP.NET和System.Data.OracleClient的一些不同
    给PDF文件添加链接和书签
    cegui0.6.0的下载、安装、配置及samples浏览
  • 原文地址:https://www.cnblogs.com/A-Long-Way-Chris/p/3539679.html
Copyright © 2020-2023  润新知