[FIXED] Das Laden von Dateien funktioniert nicht mit NSURLSession über FTP

Ausgabe

FTP-Download über URL-Sitzung funktioniert nicht. Ich habe versucht, den folgenden Code zu verwenden.

Ansatz 1

NSURL *url_upload = [NSURL URLWithString:@"ftp://user:[email protected]:/usr/path/file.json"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url_upload];
[request setHTTPMethod:@"PUT"];

NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSURL *docsDirURL = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent:@"prova.zip"]];

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 30.0;
sessionConfig.timeoutIntervalForResource = 60.0;
sessionConfig.allowsCellularAccess = YES;
sessionConfig.HTTPMaximumConnectionsPerHost = 1;
NSURLSession *upLoadSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionUploadTask *uploadTask = [upLoadSession uploadTaskWithRequest:request fromFile:docsDirURL];
[uploadTask resume];

Ansatz 2

NSURL *url = [NSURL URLWithString:@"ftp://121.122.0.200:/usr/path/file.json"];
NSString * utente = @"xxxx";
NSString * codice = @"xxxx";
NSURLProtectionSpace * protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:url.host port:[url.port integerValue] protocol:url.scheme realm:nil authenticationMethod:nil];

NSURLCredential *cred = [NSURLCredential
                         credentialWithUser:utente
                         password:codice
                         persistence:NSURLCredentialPersistenceForSession];


NSURLCredentialStorage * cred_storage ;
[cred_storage setCredential:cred forProtectionSpace:protectionSpace];

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.URLCredentialStorage = cred_storage;
sessionConfiguration.allowsCellularAccess = YES;

NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];

NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url];
[downloadTask resume];

Der Fehler, den ich bekomme, ist wie folgt:

Die angeforderte URL wurde auf diesem Server nicht gefunden

Aber die gleiche URL funktioniert im Terminal mit dem SCP-Befehl und die Datei wird erfolgreich heruntergeladen

Lösung

Zunächst sollten Sie in Betracht ziehen, von ftpto sftpoder auf httpsdas Protokoll umzusteigen, da diese viel sicherer sind und einige andere Probleme lösen.

Abgesehen davon ist das ftpProtokoll in iOS nicht streng verboten (im Gegensatz zu, sagen wir, http), und Sie können es immer noch frei verwenden. Es ist jedoch NSURLSessionnicht darauf ausgelegt, standardmäßig mit FTP- Upload – Aufgaben zu arbeiten. Sie müssen also entweder einen Custom implementieren, der eine NSURLProtocolsolche Anfrage übernimmt, oder einfach andere Mittel verwenden, ohne NSURLSession.

In beiden Fällen müssen Sie sich auf die veraltete Core Foundation API für FTP-Streams verlassen. Erstellen Sie zunächst eine CFWriteStream, die auf die Ziel-URL auf Ihrem FTP-Server verweist, wie folgt:

CFWriteStreamRef writeStream = CFWriteStreamCreateWithFTPURL(kCFAllocatorDefault, (__bridge CFURLRef)uploadURL);
NSOutputStream *_outputStream = (__bridge_transfer NSOutputStream *)writeStream;

Geben Sie im neu erstellten Objekt den Benutzernamen und das Kennwort des Benutzers an:

[_outputStream setProperty:login forKey:(__bridge NSString *)kCFStreamPropertyFTPUserName];
[_outputStream setProperty:password forKey:(__bridge NSString *)kCFStreamPropertyFTPPassword];

Erstellen Sie als Nächstes eine NSInputStreammit der URL zu der Quelldatei, in die Sie hochladen möchten (es ist nicht unbedingt erforderlich, den Eingabeteil an die Streams-API zu binden, aber ich finde es konsistent, da Sie sich sowieso mit Streams befassen müssen):

NSInputStream *_inputStream = [NSInputStream inputStreamWithURL:fileURL];

Jetzt der komplizierte Teil. Wenn es um Streams mit Remote-Ziel geht, müssen Sie asynchron mit ihnen arbeiten, aber dieser Teil der API ist uralt, sodass er nie Blöcke und andere praktische Funktionen des modernen FoundationFrameworks übernommen hat. Stattdessen müssen Sie den Stream in a planen NSRunLoopund warten, bis er den gewünschten Status an das delegateObjekt des Streams meldet:

_outputStream.delegate = self;
NSRunLoop *loop = NSRunLoop.currentRunLoop;
[_outputStream scheduleInRunLoop:loop forMode:NSDefaultRunLoopMode];
[_outputStream open];

stream:handleEvent:Nun wird das Delegate-Objekt über die Methode über alle Änderungen im Status des Streams benachrichtigt . Sie sollten die folgenden Status verfolgen:

  • NSStreamEventOpenCompleted– Der Ausgangsstrom hat gerade eine Verbindung mit dem Zielpunkt hergestellt. Hier können Sie den Eingabestrom öffnen oder einige andere Vorbereitungen treffen, die kurz vor dem Schreiben der Daten auf den FTP-Server relevant wurden;
  • NSStreamEventHasSpaceAvailable– Der Ausgabestrom ist bereit, die Daten zu empfangen. Hier schreiben Sie die Daten tatsächlich an das Ziel;
  • NSStreamEventErrorOccurred– jede Art von Fehler, der während der Datenübertragung / Verbindung auftreten kann. Hier sollten Sie die Verarbeitung der Daten stoppen.

Beachten Sie, dass Sie nicht eine ganze Datei auf einmal hochladen möchten, erstens, weil es leicht zu einem Speicherüberlauf auf einem mobilen Gerät kommen kann, und zweitens, weil die entfernte Datei möglicherweise nicht jedes gesendete Byte sofort verbraucht. In meiner Implementierung sende ich die Daten mit Blöcken von 32 KB:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {   
    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            [_inputStream open];
            return;
        case NSStreamEventHasSpaceAvailable:
            if (_dataBufferOffset == _dataBufferLimit) {
                NSInteger bytesRead = [_inputStream read:_dataBuffer maxLength:kDataBufferSize];
                
                switch (bytesRead) {
                    case -1:
                        [self p_cancelWithError:_inputStream.streamError];
                        return;
                    case 0:
                        [aStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
                        // The work is done
                        return;
                    default:
                        _dataBufferOffset = 0;
                        _dataBufferLimit = bytesRead;
                }
            }
            
            if (_dataBufferOffset != _dataBufferLimit) {
                NSInteger bytesWritten = [_outputStream write:&_dataBuffer[_dataBufferOffset]
                                                    maxLength:_dataBufferLimit - _dataBufferOffset];
                if (bytesWritten == -1) {
                    [self p_cancelWithError:_outputStream.streamError];
                    return;
                } else {
                    self.dataBufferOffset += bytesWritten;
                }
            }
            return;
        case NSStreamEventErrorOccurred:
            [self p_cancelWithError:_outputStream.streamError];
            return;
        default:
            break;
    }
}

An der Zeile mit // The work is doneKommentar gilt die Datei als vollständig hochgeladen.


Vorausgesetzt, wie komplex dieser Ansatz ist und dass es nicht wirklich machbar ist, alle Teile davon in eine einzige SO-Antwort zu packen, habe ich hier im Wesentlichen eine Hilfsklasse zur Verfügung gestellt . Sie können es so einfach im Client-Code verwenden:

NSURL *filePathURL = [NSBundle.mainBundle URLForResource:@"895971" withExtension:@"png"];
NSURL *uploadURL = [[NSURL URLWithString:@"ftp://ftp.dlptest.com"] URLByAppendingPathComponent:filePathURL.lastPathComponent];
TDWFTPUploader *uploader = [[TDWFTPUploader alloc] initWithFileURL:filePathURL
                                                         uploadURL:uploadURL
                                                         userLogin:@"dlpuser"
                                                      userPassword:@"rNrKYTX9g7z3RgJRmxWuGHbeu"];
[uploader resumeWithCallback:^(NSError *_Nullable error) {
    if (error) {
        NSLog(@"Error: %@", error);
    } else {
        NSLog(@"File uploaded successfully");
    }
}];

Es muss nicht einmal beibehalten werden, da die Klasse einen Thread erzeugt, der die Instanz behält, bis die Arbeit erledigt ist. Ich habe den Ausnahmefällen nicht allzu viel Aufmerksamkeit geschenkt, also lassen Sie es mich gerne wissen, wenn es einige Fehler enthält oder das erforderliche Verhalten nicht erfüllt.

BEARBEITEN

Bei GETAnfragen besteht der einzige Unterschied zu anderen Protokollen darin, dass Sie Login und Passwort als Teil der URL übergeben und keine sicheren Mittel verwenden können, um dasselbe zu tun. Abgesehen davon funktioniert es unkompliziert:

NSURLComponents *components = [NSURLComponents componentsWithString:@"ftp://121.122.0.200"];
components.path = @"/usr/path/file.json";
components.user = @"user";
components.password = @"pwd";
[[NSURLSession.sharedSession dataTaskWithURL:[components URL] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable
response, NSError * _Nullable error) {
    NSLog(@"%@", response);
}] resume];


Beantwortet von –
The Dreams Wind


Antwort geprüft von –
Pedro (FixError Volunteer)

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like