すべてのプロダクト
Search
ドキュメントセンター

Object Storage Service:再開可能ダウンロード

最終更新日:Dec 19, 2023

再開可能なダウンロードを使用すると、中断したオブジェクトのダウンロードを、中断した位置から再開できます。 中断されたダウンロードが再開されると、ダウンロードされた部分はスキップされ、残りの部分のみがダウンロードされる。 これにより、時間とトラフィックが節約されます。

処理中

アプリを使用して携帯電話でビデオをダウンロードすると、ダウンロード中にネットワークがWi-FiからCellularに切り替わると、アプリは自動的にダウンロードを中断します。 再開可能なダウンロードを有効にした後、ネットワークがセルラーからWi-Fiに切り替わると、ダウンロードタスクが中断された位置からビデオがダウンロードされます。

次の図は、再開可能なダウンロードの鮮明な説明です。

再開可能ダウンロードのプロセスを次の図に示します。

image

ヘッダーの説明

  • HTTP 1.1はRangeヘッダーをサポートしています。 範囲ヘッダーを使用すると、ダウンロードするデータの範囲を指定できます。 Rangeヘッダーの値の形式を次の表に示します。

    データ範囲

    説明

    範囲: bytes=100-

    ダウンロードがバイト101から開始し、最後のバイトで停止するように指定します。

    範囲: バイト=100-200

    ダウンロードがバイト101から開始し、バイト201で停止することを指定します。 ほとんどの場合、このタイプの範囲指定は、大きなビデオなどの大きなオブジェクトのマルチパート送信に使用されます。

    範囲: バイト=-100

    最後の100バイトをダウンロードします。

    範囲: バイト=0-100、200-300

    複数のダウンロード範囲を同時に指定します。

  • Resembleダウンロードでは、If-Matchヘッダーを使用して、サーバー上のオブジェクトがETagヘッダーに基づいて変更されているかどうかを確認します。

  • クライアントがリクエストを開始すると、RangeおよびIf-Matchヘッダーがリクエストに含まれます。 OSSサーバーは、リクエストで指定されたETagがオブジェクトのETagと一致するかどうかをチェックします。 Etagsが一致しない場合、OSSはHTTP 412 Precondition Failedステータスコードを返します。

  • OSSサーバーは、GetObjectリクエストに対して、RangeIf-MatchIf-None-MatchIf-Modified-SinceIf-Unmodified-Sinceのヘッダーをサポートしています。 再開可能ダウンロードを使用して、モバイルデバイスのOSSからリソースをダウンロードできます。

サンプルコード

重要

次のサンプルコードは参考用です。 本番プロジェクトでは使用しないことを推奨します。 自分でコードをダウンロードするか、サードパーティのオープンソースのダウンロードソフトウェアを使用できます。

次のサンプルコードは、OSS SDK for iOSで再開可能ダウンロードを使用する方法の例を示しています。

#import "DownloadService.h"
# import "OSSTestMacros.h"

@ implementation DownloadRequest

@ end

@ implementationチェックポイント

- (instancetype)copyWithZone :( NSZone *)zone {
    Checkpoint * other = [[[self class] allocWithZone:zone] init];

    other.etag = self.etag;
    other.totalExpectedLength = self.totalExpectedLength;

    その他を返します。}

@ end


@ interface DownloadService()<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

@ property (nonatomic, strong) NSURLSession * session; // ネットワークセッション。 
@ property (non atomic, strong) NSURLSessionDataTask * dataTask; // データ要求タスク。 
@ property (nonatomic, copy) DownloadFailureBlock failure; // リクエストの失敗。 
@ property (nonatomic, copy) DownloadSuccessBlock success; // リクエストの成功。 
@ property (non atomic, copy) DownloadProgressBlock progress; // ダウンロードの進行状況。 
@ property (nonatomic, copy) Checkpoint * checkpoint; // チェックポイントファイル。 
@ property (非アトミック、コピー) NSString * requestURLString; // ダウンロード要求で使用されるオブジェクトリソースURL。 
@ property (非アトミック、コピー) NSString * headURLString; // HEAD要求で使用されるオブジェクトリソースURL。 
@ property (nonatomic, copy) NSString * targetPath; // オブジェクトが格納されるパス。 
@ property (nonatomic, assign) unsigned long totalReceivedContentLength; // ダウンロードしたコンテンツのサイズ。 
@ property (nonatomic, strong) dispatch_semaphore_t semaphore;

@ end

@ implementation DownloadService

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSURLSessionConfiguration * conf = [NSURLSessionConfiguration defaultSessionConfiguration];
        conf.timeoutIntervalForRequest = 15;

        NSOperationQueue * processQueue = [NSOperationQueue new];
        _session = [NSURLSession sessionWithConfiguration:conf delegate:self delegateQueue:processQueue];
        _semaphore = dispatch_semaphore_create(0);
        _checkpoint = [[Checkpoint alloc] init];
    }
    自己を返す。}
// DownloadRequestはダウンロードロジックの中核です。 
+ (instancetype)downloadServiceWithRequest :( DownloadRequest *)request {
    DownloadService * service = [[DownloadService alloc] init];
    if (サービス) {
        service.failure = request.failure;
        service.success = request.success;
        service.requestURLString = request.sourceURLString;
        service.headURLString = request.headURLString;
        service.tar getPath = request.downloadFilePath;
        service.progress = request.downloadProgress;
        if (request.checkpoint) {
            service.checkpoint = request.checkpoint;
        }
    }
    帰りサービス;
}

/**
 * Headメソッドを呼び出してオブジェクト情報を取得します。 OSSは、オブジェクトのETagをローカルチェックポイントファイルに格納されているETagと比較し、比較結果を返します。 
 * /
- (BOOL)getFileInfo {
    __ブロックBOOL再開可能=NO;
    NSURL * url = [NSURL URLWithString:self.headURLString];
    NSMutableURLRequest * request = [[NSMutableURLRequest alloc]initWithURL:url];
    [リクエストsetHTTPMethod:@ "HEAD"];
    // オブジェクトに関する情報を処理します。 たとえば、ETagは再開可能なアップロード中の事前チェックに使用され、Content-Lengthヘッダーはダウンロードの進行状況を計算するために使用されます。 
    NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@ "オブジェクトメタデータの取得に失敗しました。 エラー: % @ ", エラー);
        } else {
            NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
            NSString * etag = [httpResponse.allHeaderFields objectForKey:@ "Etag"];
            if ([self.checkpoint.etag isEqualToString:etag]) {
                resumable = YES;
            } else {
                resumable = NO;
            }
        }
        dispatch_semaphore_signal(self.semaphore);
    }];
    [タスク再開];

    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    resumableを返します;
}

/**
 * ローカルファイルのサイズを照会します。 
 * /
- (unsigned long long)fileSizeAtPath :( NSString *)filePath {
    unsigned long long fileSize = 0;
    NSFileManager * dfm = [NSFileManager defaultManager];
    if ([dfm fileExistsAtPath:filePath]) {
        NSError * error = nil;
        NSDictionary * attributes = [dfm attributesOfItemAtPath:filePathエラー:&エラー];
        if (!error && attributes) {
            fileSize = attributes.fileSize;
        } else if (エラー) {
            NSLog(@ "error: % @" 、エラー);
        }
    }

    fileSizeを返します。}

- (void)resume {
    NSURL * url = [NSURL URLWithString:self.requestURLString];
    NSMutableURLRequest * request = [[NSMutableURLRequest alloc]initWithURL:url];
    [リクエストsetHTTPMethod:@ "GET"];

    BOOL Resumable= [self getFileInfo]; // 再開可能フィールドの値がNOの場合、再開可能ダウンロード条件は満たされません。 
    if (再開可能) {
        self.totalReceivedContentLength = [self fileSizeAtPath:self.tar getPath];
        NSString * requestRange = [NSString stringWithFormat:@ "bytes=% llu-", self.totalReceivedContentLength];
        [リクエストsetValue:requestRange forHTTPHeaderField:@ "Range"];
    } else {
        self.totalReceivedContentLength = 0;
    }

    if (self.totalReceivedContentLength == 0) {
        [[NSFileManager defaultManager] createFileAtPath:self.tar getPathの内容: nil attributes:nil];
    }

    self.dataTask = [self.session dataTaskWithRequest: リクエスト];
    [self.dataTaskの履歴書];
}

- (void)pause {
    [self.dataTaskキャンセル];
    self.dataTask = nil;
}

- (void)cancel {
    [self.dataTaskキャンセル];
    self.dataTask = nil;
    [self removeFileAtPath: self.tar getPath];
}

- (void)removeFileAtPath :( NSString *)filePath {
    NSError * error = nil;
    [[NSFileManager defaultManager] removeItemAtPath:self.tar getPathエラー:&エラー];
    if (error) {
        NSLog(@ "エラーのあるファイルを削除: % @" 、エラー);
    }
}

# pragmaマーク-NSURLSessionDataDelegate

- (void)URLSession :( NSURLSession *) セッションタスク :( NSURLSessionTask *) タスク
// ダウンロードタスクが完了したかどうかを確認し、結果を上位レイヤーに返します。 
didCompleteWithError :( nullable NSError *)error {
    NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)task.response;
    if ([httpResponse isKindOfClass:[NSHTTPURLResponseクラス]]) {
        if (httpResponse.statusCode == 200) {
            self.checkpoint.etag = [[httpResponse allHeaderFields] objectForKey:@ "Etag"];
            self.checkpoint.totalExpectedLength = httpResponse.expectedContentLength;
        } else if (httpResponse.statusCode == 206) {
            self.checkpoint.etag = [[httpResponse allHeaderFields] objectForKey:@ "Etag"];
            self.checkpoint.totalExpectedLength = self.totalReceivedContentLength + httpResponse.expectedContentLength;
        }
    }

    if (error) {
        if (self.failure) {
            NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithDictionar y:error.us erInfo];
            [userInfo oss_setObject:self.checkpoint forKey:@ "checkpoint"];

            NSError * tError = [NSError errorWithDomain:error.domainコード: error.code userInfo:userInfo];
            self.failure(tError);
        }
    } else if (self.success) {
        self.success(@{@ "status": @ "success"});
    }
}

- (void)URLSession :( NSURLSession *)session dataTask :( NSURLSessionDataTask *)dataTask didReceiveResponse :( NSURLResponse *)response completionHandler :( void (^)(NSURLSessionResponseDisposition))completionHandler
{
    NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)dataTask.response;
    if ([httpResponse isKindOfClass:[NSHTTPURLResponseクラス]]) {
        if (httpResponse.statusCode == 200) {
            self.checkpoint.totalExpectedLength = httpResponse.expectedContentLength;
        } else if (httpResponse.statusCode == 206) {
            self.checkpoint.totalExpectedLength = self.totalReceivedContentLength + httpResponse.expectedContentLength;
        }
    }

    completionHandler(NSURLSessionResponseAllow);
}
// 受信したネットワークデータを追加アップロードを使用してオブジェクトに書き込み、ダウンロードの進行状況を更新します。 
- (void)URLSession :( NSURLSession *)session dataTask :( NSURLSessionDataTask *)dataTask didReceiveData :( NSData *)data {

    NSFileHandle * fileHandle = [NSFileHandleForWritingAtPath: self.tar getPath];
    [fileHandle seekToEndOfFile];
    [fileHandle writeData: データ];
    [fileHandle closeFile];

    self.totalReceivedContentLength += data.length;
    if (self.progress) {
        self.progress(data.length、self.totalReceivedContentLength、self.checkpoint.totalExpectedLength);
    }
}

@ end 
  • 次のサンプルコードは、DownloadRequestの定義方法の例を示しています。

    #import <Foundation/Foundation.h>
    
    typedef void(^ DownloadProgressBlock)(int64_t bytesReceived, int64_t totalBytesReceived, int64_t totalBytesExpectToReceived);
    typedef void(^ DownloadFailureBlock)(NSError * エラー);
    typedef void(^ DownloadSuccessBlock)(NSDictionary * 結果);
    
    @ interfaceチェックポイント: NSObject<NSCopying>
    
    @ property (nonatomic, copy) NSString * etag; // リソースのETag値。 
    @ property (nonatomic, assign) unsigned long totalExpectedLength; // オブジェクトの合計サイズ。 
    
    @ end
    
    @ interface DownloadRequest : NSObject
    
    @ property (非アトミック、コピー) NSString * sourceURLString; // ダウンロードのURL。 
    
    @ property (nonatomic, copy) NSString * headURLString; // オブジェクトのメタデータを取得するために使用されるURL。 
    
    @ property (nonatomic, copy) NSString * downloadFilePath; // ダウンロードされたオブジェクトが保存されるローカルパス。 
    
    @ property (nonatomic, copy) DownloadProgressBlock downloadProgress; // ダウンロードの進行状況。 
    
    @ property (non atomic, copy) DownloadFailureBlock failure; // ダウンロードが失敗した後に送信されるコールバック。 
    
    @ property (non atomic, copy) DownloadSuccessBlock success; // ダウンロードが成功した後に送信されるコールバック。 
    
    @ property (nonatomic, copy) Checkpoint * checkpoint; // オブジェクトのETag値を格納するチェックポイントファイル。 
    
    @ end
    
    
    @ interface DownloadService : NSObject
    
    + (instancetype)downloadServiceWithRequest :( DownloadRequest *)request;
    
    /**
     * ダウンロードを開始します。 
     * /
    - (void) 再開;
    
    /**
     * ダウンロードを一時停止します。 
     * /
    - (void) 一時停止;
    
    /**
     * ダウンロードをキャンセルします。 
     * /
    - (void) キャンセル;
    
    @ end 
  • 次のサンプルコードは、上位レイヤーの呼び出しコードの例を示しています。

    - (void)initDownloadURL {
        OSSPlainTextAKSKPairCredentialProvider * pCredential = [[OSSPlainTextAKSKPairCredentialProvider alloc] initWithPlainTextAccessKey: OSS_ACCESSKEYID secretKey:OSS_SECRETKEY_ID];
        _mClient = [[OSSClient alloc] initWithEndpoint:OSS_ENDPOINT credentialProvider:pCredential];
    
        // GETリクエストの署名付きURLを生成します。 
        OSSTask * downloadURLTask = [_mClient presignConstrainURLWithBucketName:@ "aliyun-dhc-shanghai" withObjectKey:OSS_DOWNLOAD_FILE_NAME withExpirationInterval:1800];
        [downloadURLTask waitUntilFinished];
        _downloadURLString = downloadURLTask.result;
    
        // HEADリクエストの署名付きURLを生成します。 
        OSSTask * headURLTask = [_mClient presignConstrainURLWithBucketName:@ "aliyun-dhc-shanghai" withObjectKey:OSS_DOWNLOAD_FILE_NAME httpMethod:@ "HEAD" withExpirationInterval:1800 withParameters:nil];
        [headURLTask waitUntilFinished];
    
        _headURLString = headURLTask.result;
    }
    
    - (IBAction)resumeDownloadClicked :( id)sender {
        _downloadRequest = [DownloadRequest new];
        _downloadRequest.sourceURLString = _downloadURLString; // リソースURLを指定します。 
        _downloadRequest.headURLString = _headURLString;
        NSString * documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        _downloadRequest.downloadFilePath = [documentPath stringByAppendingPathComponent:OSS_DOWNLOAD_FILE_NAME]; // ダウンロードするローカルパスを指定します。 
    
        __weak typeof(self) wSelf = self;
        _downloadRequest.downloadProgress = ^(int64_t bytesReceived, int64_t totalBytesReceived, int64_t totalBytesExpectToReceived) {
            // totalBytesReceivedは、クライアントによってキャッシュされたバイト数です。 totalBytesExpectToReceivedは、ダウンロードする必要があるバイトの総数です。 
            dispatch_async(dispatch_get_main_queue()), ^ {
                __strong typeof(self) sSelf = wSelf;
                CGFloat fProgress = totalBytesReceived * 1.f / totalBytesExpectToReceived;
                sSelf.progressLab.text = [NSString stringWithFormat:@ "%.2f % % %", fProgress * 100];
                sSelf.progressBar.progress = fProgress;
            });
        };
        _downloadRequest.failure = ^(NSError * エラー) {
            __strong typeof(self) sSelf = wSelf;
            sSelf.checkpoint = error.us erInfo[@ "checkpoint"];
        };
        _downloadRequest.success = ^(NSDictionary * result) {
            NSLog(@ "ダウンロードは成功しました。");
        };
        _downloadRequest.checkpoint = self.checkpoint;
    
        NSString * titleText = [[_downloadButton titleLabel] text];
        if ([titleText isEqualToString:@ "download"]) {
            [_downloadButton setTitle:@ "pause" forState: UIControlStateNormal];
            _downloadService = [DownloadService downloadServiceWithRequest:_downloadRequest];
            [_downloadService再開];
        } else {
            [_downloadButton setTitle:@ "download" forState: UIControlStateNormal];
            [_downloadService一時停止];
        }
    }
    
    - (IBAction)cancelDownloadClicked :( id)sender {
        [_downloadButton setTitle:@ "download" forState: UIControlStateNormal];
        [_downloadServiceキャンセル];
    } 
説明

チェックポイントファイルは、ダウンロードが一時停止またはキャンセルされたときに障害コールバックから取得できます。 ダウンロードを再起動すると、チェックポイントファイルをDownloadRequestにインポートできます。その後、DownloadServiceはチェックポイントファイルを使用して整合性検証を実行します。