FtpWebRequest-Tutorial

    • VB.NET

    Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

      FtpWebRequest-Tutorial

      Hier eine kleine Einführung in FTP mittels FtpWebRequest. Gugge auch [VB 2010] FTP Tutorial Uploaden und downloaden - dort wird auf paar allgemeinere Aspekte von Ftp eingegangen, zB wo man einen eigenen Ftp-Account bekommt oder einen Ftp-Browser, und die WebClient-Klasse ist auch kurz angerissen.
      V.a. wird dort auch das Sicherheits-Risiko bei Ftp angesprochen, denn noch immer ists so, dass bei Ftp die Account-Daten unverschlüsselt übertragen werden - was es einem Angreifer unerhört leicht macht, die volle Kontrolle über den Account zu gewinnen.

      Ich jdfs. habe kürzlich festgestellt: manche Server bieten auch öffentliche FTP-Directories (readonly natürlich), mit denen man ohne Passwort-Angabe kommunizieren kann.
      Das ermöglicht mir, eine sofort lauffähige Solution einzustellen, die FTP in Action zeigt, auch für Leute, die noch über keinen eigenen Ftp-Account verfügen.
      Ich habe nur 2 Ftp-Befehle implementiert: WebRequestMethods.Ftp.ListDirectoryDetails und .DownloadFile
      Die WebRequestMethods.Ftp - Klasse hält noch viele weitere FTP-Befehlskonstanten bereit, als da wären:


      Also zunächst muß man einen WebRequest erzeugen - das geht ausnahmsweise nicht mittm Schlüsselwort New, sondern die WebRequest-Klasse hat eine Public Shared Function .Create(uri), welche einen WebRequest zurückgibt.
      Nun gibt es aber drei verschiedene WebRequest-Klassen, die alle von Webrequest erben:

      Und die Public Shared Function Webrequest.Create()-Methode entscheidet selbst anhand des im Uri angegebenen Protokolls ("file://", "http://" oder "ftp://"), welchen Webrequest-Erben sie zurückgibt: FileWebrequest, FtpWebrequest oder HttpWebrequest.
      Die verschiedenen Webrequest-Erben unterscheiden sich zwar, aber in den für uns wesentlichen Belangen sind sie absolut gleich, sodass wir uns gar nicht drum kümmern, ob da nun wirklich ein FtpWebrequest vorliegt oder nicht.

      Download File
      Für den Download benötigt man den Request, dessen Response, und von der Response den ResponseStream.
      Letzterer ist ein Stream wie man ihn (hoffentlich!!) zB als FileStream bereits von Dateizugriffen kennt.
      Man kann einen Stream zeilenweise auslesen, oder paketweise gepuffert. Beim Download lesen wir den ResponseStream paketweise aus - das ist auch auf nicht-textuelle Dateien anwendbar, bei denen zeilenweises Auslesen ausscheidet:

      VB.NET-Quellcode

      1. Private Sub DownLoadItem(ByVal fileName As String)
      2. Dim request = WebRequest.Create(txtFtpSource.Text & "/"c & fileName)
      3. request.Method = WebRequestMethods.Ftp.DownloadFile
      4. If ckCredentials.Checked Then request.Credentials = New NetworkCredential(txtUser.Text, txtPassword.Text)
      5. Dim dest = Path.Combine(lbDestinationFolder.Text, fileName)
      6. Using resp = request.GetResponse(), strmResp = resp.GetResponseStream, _
      7. strmFile = New FileStream(dest, FileMode.Create)
      8. Dim buf(1023) As Byte
      9. Do
      10. Dim read = strmResp.Read(buf, 0, buf.Length)
      11. If read = 0 Then Exit Do 'end of Transmission
      12. strmFile.Write(buf, 0, read)
      13. Loop
      14. End Using
      15. End Sub
      Jo - das mittm WebRequest habich ja schon erklärt - man sieht, wie der WebRequest mit einem flugs zusammengebauten uri created wird, dann wird die auszuführende Methode angegeben, ggfs. werden Passwort-Informationen ("Credentials") angegeben (Zeilen #2 - #4).
      In #6 - #7 wird dann die Response geholt (das ist die eigentlichen Anforderung ins INet), dann der ResponseStream geholt, und ausserdem der Schreib-Stream strmFile erstellt, wo die gelesenen Bytes dann hineingeschrieben werden.
      Zeilen #9 - #13 sind die typische Kopier-Schleife für paketweises kopieren von Streams: strmResp.Read() fordert auf, den gesamten Puffer vollzulesen, und gibt zurück, wieviele Bytes tatsächlich gelesen wurden - spätestens beim letzten Paket wird der Puffer ja nicht mehr komplett befüllt.
      Ja, und genau so viele Bytes, wie gelesen wurden, müssen natürlich auch in strmFile weggeschrieben werden.
      Falls ühaupt kein Byte gelesen wurde, wird der loop verlassen.

      List DirectoryDetails
      Beim Listing eines Directories ändert sich am Request im Grunde garnichts - nur eine annere Methode ist angegeben (#5):

      VB.NET-Quellcode

      1. Private Sub ListDirectoryDetails(ByVal url As String)
      2. txtFtpSource.Text = url
      3. Dim request = WebRequest.Create(url)
      4. If ckCredentials.Checked Then request.Credentials = New NetworkCredential(txtUser.Text, txtPassword.Text)
      5. request.Method = WebRequestMethods.Ftp.ListDirectoryDetails
      6. Using resp = request.GetResponse(), sr = New StreamReader(resp.GetResponseStream)
      7. While Not sr.EndOfStream
      8. Dim line As String= sr.ReadLine
      9. Console.WriteLine(line)
      10. ParseLine(line)
      11. End While
      12. End Using
      13. End Sub
      Ja, und die Schleife liest nun nicht paketweise Bytes in einen Puffer, sondern ein StreamReader liest jeweils eine kleine Menge Bytes und konvertiert die in einen String, welcher eine Textzeile darstellt. (#7 - #11). Was mit der Zeile geschieht, ist in dieser Methode noch garnet ersichtlich, denn die Zeilen sind recht aufwändig zu interpretieren - das findet in einer gesonderten Parse-Funktion statt:

      VB.NET-Quellcode

      1. Private _RgxWordAndSpaces As New Regex(".+? +") 'finde viele Buchstaben, gefolgt von vielen Spaces
      2. Private Sub ParseLine(ByVal line As String)
      3. 'line mag so aussehen: "-rw-r--r-- 1 700 101 17145 Sep 13 2000 README.txt"
      4. 'das sind 9 spalten, von denen _RgxWordAndSpaces die ersten 8 spalten sicher matcht
      5. 'Bedeutung der columns: Attributes, k.A., k.A., k.A., Size (ungültig bei Directories), Monat, Tag, Jahr, fileName
      6. Dim cols As MatchCollection = _RgxWordAndSpaces.Matches(line)
      7. Dim attrs = cols(0).Value '(Attributes, zb. "-rw-r--r--")
      8. Dim size = -1 'default Wert -1: ungültige size
      9. 'size befindet sich in cols(4) (zb. "17145")
      10. 'wird nur dann als size ausgewertet, wenn attrs(0) ein '-' ist (sonst ungültig)
      11. If attrs(0) = "-"c Then size = Integer.Parse(cols(4).Value)
      12. 'Datum setzt sich zusammen aus den matches #5-#7 (zb. "Sep 13 2000 ")
      13. Dim datum = DateTime.Parse(cols(5).Value & cols(6).Value & cols(7).Value, CultureInfo.InvariantCulture)
      14. 'fileName liegt hinter der 8. column (zb. "README.txt")
      15. Dim fileName = line.Substring(cols(7).Index + cols(7).Length).Trim
      16. 'jetzt sind alle daten zusammen, um eine FtpItemRow zu adden (die bedeutung der spalten #1-#3 kennichnich)
      17. FtpItemDts.FtpItem.AddFtpItemRow(attrs, cols(1).Value, cols(2).Value, cols(3).Value, size, datum, fileName, Nothing)
      18. End Sub
      Also _RgxWordAndSpaces ist ein Regex, der die ersten 8 Spalten einer line sicher identifiziert, und als Match in die MatchCollection aufnimmt. Nach der 8.Spalte wird es unsicher, weil Dateinamen können auch Spaces enthalten, und das würde dann als Spaltentrenner mißverstanden.
      Jo, wenn man genau schaut, versteht man glaubich, wie die relevanten Daten ermittelt werden: Attribute, ggfs. size, Datum und File-/Directory-Name.
      Mit diesen Infos wird einer typisierten DataTable eine typisierte DataRow hinzugefügt (#18), wobei auch 3 Spalten enthalten sind, deren Sinn ich garnet verstehe - aber man will ja nix unterschlagen ;).
      Auf das mit der DataTable gehe ich jetzt garnet weiter ein - wichtig ist hier nur, dass das Schema erkennbar ist, wie die Zeilen eines DirectoryListings ankommen, und wie man die Informationen extrahieren kann.

      Sample-Projekt

      Also der Button "GetDetailsFrom" lädt die DirectoryInformationen des nebenstehenden Uris herunter. Bezeichnet der Uri eine Datei statt eines Directories, so gibts keinen Fehler, sondern statt ein Directory zu listen wird halt eben nur die Info dieser einen Datei runtergeladen.
      In den DGV-Zeilen gibts je 2 Buttons, einmal wieder "GetDetails", mit dem die Anzeige sich in den betreffenden Ordner vertieft.
      Zum anderen der Button "Download". Falls das aktuelle Item hierbei keine Datei ist, gibts einen Fehler, wenn man den Download versucht.
      Aufwärts navigieren kann man über den Button "Folder Up".
      Unter dem Grid ist noch eine Logging-Listbox, die protokolliert, welche Requests ausgeführt wurden.

      Das ganze Teil arbeitet ohne Fehlerbehandlung, denn der Sinn des Rumprobierens besteht ja v.a. darin, die möglichen Fehler kennenzulernen.

      WebRequest versus WebClient
      WebClient ist ein Wrapper um die WebRequest-Funktionalität - spezialisiert auf Up-/Down-loads. Während man für jede Abfrage-Konfiguration einen eigenen WebRequest erstellen muß, kann man man einen WebClient mit verschiedenen Jobs beauftragen.
      Wies bei Wrappern so ist: Der höhere Bedien-Komfort im spezialisierten Arbeitsbereich wird erkauft durch ein geringeres Anwendungsspektrum (hier: Einschränkung auf Up-/Down-loading, Unwirtschaftlichkeit bei vielen parallellen Abfragen)
      Dateien
      • FtpRequest00.zip

        (25,4 kB, 698 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „ErfinderDesRades“ ()

      @ErfinderDesRades:

      Will das ja jetz nicht hochwühlen, aber als Tipp:

      - Der zweite Wert könnten die Unterordner -2 sein. Warum -2 hab ich keine Ahnung, stimmt aber IMMER.
      - 3. Wert ist der Benutzer, der die Ordner/Dateien hochgeladen / erstellt hat.

      Falls mir jemand das mit den -2 erklären kann, vielen Dank.
      LG
      "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

      Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
      Das mit dem -2 hat Unix-Gründe.
      Es wird ein "Unix"-System z.T. emuliert (FileZilla Server).
      Das bedeutet, dasses in jedem leeren Ordner, zwei "virtuelle" gibt.

      Quellcode

      1. .
      2. ..

      ersterer liefert den aktuellen Ordner, letzterer das Elternverzeichnis.
      Die Einführung hat mir sehr geholfen. Ich habe nur ein Problem, nämlich , dass das Jahr nicht erscheint. Die gelieferte Zeile sieht so aus:

      VB.NET-Quellcode

      1. -rw-r--r-- 1 ftp ftp 52736 Mar 13 11:19 dateiname.rpt


      Hat vielleicht jemand eine Idee, woran das liegt?
      msdn.microsoft.com/en-us/library/2h3syy57(v=vs.110).aspx
      A missing date defaults to the current date, a missing year defaults to the current year, a missing day of the month defaults to the first day of the month, and a missing time defaults to midnight.

      Also einfach Parsen, dann solltest automatisch das aktuelle Jahr erhalten, oder ist es etwa tatsächlich von letztem Jahr und nicht diesem?
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---