AWS EC2サーバー(Ubuntu 16.04)上で動作するKitura(http://www.kitura.io)を使用して、Swiftで書かれたカスタムサーバーがあります。 CA署名付きSSL証明書(https://letsencrypt.org)を使用してセキュリティを確保しています。そのため、httpsを使用してクライアントからサーバーに接続できます。クライアントはiOS(9.3)のもとでネイティブに動作します。 iOS上でURLSessionを使用してサーバーに接続します。AWS EC2からiOSアプリケーションへのダウンロード時にタイムアウトの問題
iOSクライアントに複数の大きなダウンロードを連続して行うと、クライアントタイムアウトの問題が発生します。タイムアウトは次のようになります。サーバー上で
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSErrorFailingURLStringKey=https://, _kCFStreamErrorCodeKey=-2102, NSErrorFailingURLKey=https://, NSLocalizedDescription=The request timed out., _kCFStreamErrorDomainKey=4, NSUnderlyingError=0x7f9f23d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102}}}
、タイムアウトは常にcode--内の同じ場所で発生し、彼らがブロックし、決して回復するために、特定のサーバーの要求スレッドを引き起こします。タイムアウトは、サーバスレッドがKitura RouterResponse
end
メソッドを呼び出すのと同じように発生します。すなわち、サーバスレッドがこのend
メソッドを呼び出すときにブロックします。これを考慮して、クライアントアプリケーションがタイムアウトするのは驚くべきことではありません。このコードはオープンソースですので、私はどこのサーバー・ブロックにリンクされます:https://github.com/crspybits/SyncServerII/blob/master/Server/Sources/Server/ServerSetup.swift#L146
失敗したクライアント側のテストは、次のとおりです。https://github.com/crspybits/SyncServerII/blob/master/iOS/Example/Tests/Performance.swift#L53
私はAmazon S3のようなものからのダウンロードではないのです。データは別のWebソースからサーバー上で取得され、EC2上で実行されているサーバーからhttps経由でクライアントにダウンロードされます。
例として、1.2 MBのデータをダウンロードするのに3〜4秒かかります。この1.2 MBのダウンロードのうち10個をバックトゥーバックしてみると、3つはタイムアウトします。ダウンロードはHTTPS GET要求を使用して行われます。
これらのダウンロードを最初に行うテストでは、同じデータサイズのアップロードが最初に行われます。つまり、アップロードはそれぞれ1.2 MBで10回実行されます。これらのアップロードでタイムアウトの失敗は見られませんでした。私のリクエストの
大半は仕事をするので、これはと言うと、単に問題、不適切にインストールされているSSL証明書(私はhttps://www.sslshopper.comていることをチェックしました)であることが表示されません。 iOS側の不適切なhttps設定の問題でもないようです。Amazonのおすすめ情報(https://aws.amazon.com/blogs/mobile/preparing-your-apps-for-ios-9/)を使用してアプリの.plistにNSAppTransportSecurity
設定があります。
思考?
アップデート1: 私はちょうど私のサーバーは、ローカルのUbuntu 16.04システム上で実行されている、およびそれを残りの他の要因certificate--自己署名SSLを使用して、これを試してみました。私も同じ問題が出てくる。したがって、これは明らかです。はAWSに関連していません。
アップデート2:サーバがローカルのUbuntu 16.04システム上で実行されていると 、およびSSL(単にサーバコードの1行を変更し、クライアントでHTTPSとは対照的に、HTTPを使用)を使用せずに、問題がではなく、が存在します。ダウンロードが正常に行われます。したがって、この問題はがSSLに関連することは明らかです。
Update3と: サーバがローカルのUbuntu 16.04システム上で実行されているし、再び自己署名SSL証明書を使用して、私はシンプルなcurl
クライアントを使用していました。できるだけ近く使用していたテストをシミュレートするため、ダウンロードを開始したときと同じように既存のiOSクライアントテストを中断し、サーバー上のダウンロードエンドポイントを使用したcurl
クライアントを使用して再起動しました同じ1.2MBファイルを20回ダウンロードしてください。エラーはではなく、であった。私の結論は、問題はiOSクライアントとSSLの間の相互作用に起因するということです。
Update4: 問題を再現するiOSクライアントのバージョンがより簡単になりました。私はそれを下にコピーしますが、要約すると、URLSession
のものを使用しています。同じタイムアウトの問題が発生しています(サーバは自分のローカルUbuntuシステムで自己署名SSL証明書を使用しています)。 SSLの使用を無効にすると(サーバーで使用されているhttpとSSL証明書なし)、でなく、の問題が発生します。
import Foundation
class Download : NSObject {
static let session = Download()
var authHeaders:[String:String]!
override init() {
super.init()
authHeaders = [
<snip: HTTP headers specific to my server>
]
}
func downloadFrom(_ serverURL: URL, completion:@escaping()->()) {
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.httpAdditionalHeaders = authHeaders
let session = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
var request = URLRequest(url: serverURL)
request.httpMethod = "GET"
print("downloadFrom: serverURL: \(serverURL)")
var downloadTask:URLSessionDownloadTask!
downloadTask = session.downloadTask(with: request) { (url, urlResponse, error) in
print("downloadFrom completed: url: \(String(describing: url)); error: \(String(describing: error)); status: \(String(describing: (urlResponse as? HTTPURLResponse)?.statusCode))")
completion()
}
downloadTask.resume()
}
}
extension Download : URLSessionDelegate, URLSessionTaskDelegate /*, URLSessionDownloadDelegate */ {
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}
}
Update5: やれやれ "Download.swift" という名前のファイルに
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
download(10)
}
func download(_ count:Int) {
if count > 0 {
let masterVersion = 16
let fileUUID = "31BFA360-A09A-4FAA-8B5D-1B2F4BFA5F0A"
let url = URL(string: "http://127.0.0.1:8181/DownloadFile/?fileUUID=\(fileUUID)&fileVersion=0&masterVersion=\(masterVersion)")!
Download.session.downloadFrom(url) {
self.download(count - 1)
}
}
}
}
//:ここ
はシンプルなクライアントです!私は正しい方向に進歩しています! SSL/httpsを使用していて、この問題が発生していないiOSクライアントが簡単になりました。変更は@Ankit Thakurによって提案されました:私は今URLSessionConfiguration.default
の代わりにURLSessionConfiguration.background
を使用しています。私はなぜそう思うか分からない。これはURLSessionConfiguration.default
のバグですか?たとえば、テスト中に私のアプリが明示的にバックグラウンドに入ることはありません。また、クライアントアプリケーションでこのパターンのコードを使用できるようにするにはどうすればよいかわかりません。URLSession
のこの使用方法は、URLSessionを作成した後にhttpAdditionalHeaders
を変更できないようです。 URLSessionConfiguration.background
の目的は、URLSession
がアプリの存続期間中存続することです。これは、アプリケーションの1回の起動中にHTTPヘッダーが変更される可能性があるため、私の問題です。
ここに私の新しいDownload.swiftコードがあります。私の単純な例では、他のコードは同じまま:
import Foundation
class Download : NSObject {
static let session = Download()
var sessionConfiguration:URLSessionConfiguration!
var session:URLSession!
var authHeaders:[String:String]!
var downloadCompletion:(()->())!
var downloadTask:URLSessionDownloadTask!
var numberDownloads = 0
override init() {
super.init()
// https://developer.apple.com/reference/foundation/urlsessionconfiguration/1407496-background
sessionConfiguration = URLSessionConfiguration.background(withIdentifier: "MyIdentifier")
authHeaders = [
<snip: my headers>
]
sessionConfiguration.httpAdditionalHeaders = authHeaders
session = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: OperationQueue.main)
}
func downloadFrom(_ serverURL: URL, completion:@escaping()->()) {
downloadCompletion = completion
var request = URLRequest(url: serverURL)
request.httpMethod = "GET"
print("downloadFrom: serverURL: \(serverURL)")
downloadTask = session.downloadTask(with: request)
downloadTask.resume()
}
}
extension Download : URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("download completed: location: \(location); status: \(String(describing: (downloadTask.response as? HTTPURLResponse)?.statusCode))")
let completion = downloadCompletion
downloadCompletion = nil
numberDownloads += 1
print("numberDownloads: \(numberDownloads)")
completion?()
}
// This gets called even when there was no error
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("didCompleteWithError: \(String(describing: error)); status: \(String(describing: (task.response as? HTTPURLResponse)?.statusCode))")
print("numberDownloads: \(numberDownloads)")
}
}
Update6: 私は、HTTPヘッダーの状況に対処するために、今どのように参照してください。 URLRequestのallHTTPHeaderFields
プロパティを使用できます。状況は基本的に解決されるべきです!
Update7: 背景技術が働くなぜ私は考え出したことがあります。
Any upload or download tasks created by a background session are automatically retried if the original request fails due to a timeout.
コードはクライアント側に適しています。 SessionConfigurationをデフォルトの代わりにバックグラウンドにしてみますか? 'let sessionConfiguration = URLSessionConfiguration.default' –
アイデアをありがとう。少なくとも私のコードでは、これを試すことさえかなり再構築されています。たとえば、背景を使用するように切り替えると、クロージャを使用できなくなります。デリゲートを使用する必要があります。これをしばらく作業した後で、今私が困っているのは、デリゲートメソッドで期待しているレスポンスヘッダーが得られていないことです。つまり、 'func urlSession(_ session:URLSession、task:URLSessionTask、didCompleteWithErrorエラー:エラー?)' 'task.response'に、私が期待するヘッダが含まれていません。 –
wiresharkを試してみてください。その後、wiresharkで要求ヘッダーと応答を確認し、コードをデバッグすることができます。一部のリクエストヘッダーが問題である可能性があります。コンテンツタイプやその他のリクエストヘッダーを確認してください。 –