Prosty serwerek FTP (INDY)

agent_ziemba

Cześć tu Agent_Ziemba. Jest to mój pierwszy artykuł, proszę o wyrozumiałość. Starałem się poprawić wszystkie błędy jakie
znalazłem. W tym gotowcu napiszemy sobie jak postawić prosty serwerek FTP. Możemy to zrobić na 2 sposoby:

  1. Zainstalować sobie jakiś gotowy serwerek :P (ale to nie dla nas)
  2. Napisać sobie serwerek (chyba trochę większa frajda)
    Zaczynamy

Robiłem to w Delphi7 z zainstalowanym INDY 10. Uruchamiamy sobie Delphi i wrzucamy IdFTPServer. Teraz zabieramy się za edycję
kodu.

Deklarujemy globalną zmienną Folder


var 
  Folder  :  String;


Jest to zmienna typu String. Do tej zmiennej będzie przypisana ścieżka której użyjemy jako główny folder FTP (root dir).
Następnie piszemy sobie funkcję która zamienia znaki ze stylu UNIX na styl WINDOWS. Czyli '/' na '' i '\' na '' .


function TForm1.ZamienZnaki(APath:String) :String;
var  s:String;
begin
  s:=StringReplace(APath, '/', '\', [rfReplaceAll]);
  s:=StringReplace(s, '\\', '\', [rfReplaceAll]);
  Result:=s;
end;

Teraz piszemy sobie funkcję podającą rozmiar plików:


function TForm1.Rozmiarpliku(AFile : String) : Integer;
var FStream : TFileStream;
begin
try
  FStream:=TFileStream.Create(AFile, fmOpenRead);
  try 
   Result:=Fstream.Size;
  finally
  FreeAndNil(FStream);
  end;
 except
  Result:=0;
 end;
 end;

Powyzsza funkcja otwiera strumien pliku, sprawdza jego rozmiar i przypisuje do zmiennej Result.

Każdy serwer FTP musi wyświetlić listę plików do pobrania napiszmy więc obsługę eventu <B> OnListDirectory </B>


var
 LFTPItem :TIdFTPListItem;
 SR : TSearchRec;
 SRI : Integer;
begin
  ADirectoryListing.DirFormat := doUnix;
  SRI := FindFirst(Folder + APath + '\*.*', faAnyFile - faHidden - faSysFile, SR);
  While SRI = 0 do
  begin
    LFTPItem := ADirectoryListing.Add;
    LFTPItem.FileName := SR.Name;
    LFTPItem.Size := SR.Size;
    LFTPItem.ModifiedDate := FileDateToDateTime(SR.Time);
    if SR.Attr = faDirectory then
     LFTPItem.ItemType   := ditDirectory
    else
     LFTPItem.ItemType   := ditFile;
    SRI := FindNext(SR);
  end;
  FindClose(SR);
  SetCurrentDir(Folder + APath + '\..');
end;

Teraz przydałoby się zareagować jeżeli jakiś user będzie chciał uploadować plik, napiszmy więc obsługę eventu <B> OnStoreFile</B> :


begin
 if not Aappend then
   VStream := TFileStream.Create(ZamienZnaki(Folder+AFilename),fmCreate)
 else
   VStream := TFileStream.Create(ZamienZnaki(Folder+AFilename),fmOpenWrite)
end;

Teraz uzupełnimy event <B> OnRetrieveFile </B>, który będzie odpowiedzialny za downloadowanie plików:


begin
  VStream := TFileStream.Create(ZamienZnaki(Folder+AFilename),fmOpenRead);
end;

Z kolei w evencie <B> OnMakeDirectory </B> umieścimy następujący kod:


begin
  if not ForceDirectories(ZamienZnaki(Folder + VDirectory)) then
  begin
    Raise Exception.Create('Nie mozna utworzyc katalogu');
  end;
end;

Jeżeli user będzie chciał zmienić katalog roboczy to zadziała event <B> OnChangeDirectory </B>, który to uzupełniamy o poniższy kod:


begin
  ASender.CurrentDir := VDirectory;
end;

Poniższy kod będzie pobierał rozmiar pliku w systemie plików serwera. Wrzucamy go do eventu <B> OnGetFileSize </B>


Var
 LFile : String;
begin 
 LFile := ZamienZnaki( Folder + AFilename );
 try
 If FileExists(LFile) then
   VFileSize :=  RozmiarPliku(LFile)
 else
   VFileSize := 0;
 except
   VFileSize := 0;
 end;
end;

Teraz uzupełnimy event <B> OnDeleteFile </B> odpowiedzialny za kasowanie plików z serwera.


begin
  DeleteFile(ZamienZnaki(Folder+ASender.CurrentDir+'\'+APathname));
end;

Na koniec tego gotowca zostawiłem logowanie. Kod Logowania należy wrzucić do eventu <B> OnUserLogin</B> .


begin
  if (AUsername='maniek') and (APassword='pankoski') then begin
   AAuthenticated := True;
 end
 else begin
  AAuthenticated := False;
 end;
end;

Trochę wyjaśnienia. Jeżeli użytkownik poda nazwę użytkownika maniek i hasło pankoski to uzyska dostęp do serwera,
jeżeli poda coś innego to jego logowanie zostanie odrzucone. To był tylko przykład, jeżeli chcecie mieć bardzo niewielu
userów to możecie wrzucić nazwy userów do kodu, ale nie polecam tego z kilku powodów:

  1. Trudność ze zmianą hasła usera (powtórna kompilacja)
  2. Trudność z dodaniem usera (powtórna kompilacja)
  3. Trudność z usunięciem usera (powtórna kompilacja)
    Radze zrobić jakiś plik z hasłami i podczas logowania sprawdzać login i hasło.
    Jeszcze tylko musimy uruchamiać i zamykać nasz serwerek. Wrzucamy na formę edita i dwa buttony. W edicie będzie podawana
    ścieżka do katalogu wirtualnego. Button1 będzie uruchamiał serwer:

begin
  Folder:=Edit1.Text;
  IdFTPServer1.Active:=True;
end;

Button2 będzie zamykał serwer


begin
  IdFTPServer1.Active:=False;
end;

A oto cały listing unita:


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  IdBaseComponent, IdComponent, IdTCPServer, IdCmdTCPServer, IdFTPList,
  IdExplicitTLSClientServerBase, IdFTPServer, StdCtrls, IdFTPListOutput,
  IdCustomTCPServer;

type
  TForm1 = class(TForm)
    IdFTPServer1: TIdFTPServer;
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Label1: TLabel;
    procedure IdFTPServer1UserLogin(ASender: TIdFTPServerContext;
      const AUsername, APassword: string; var AAuthenticated: Boolean);
    procedure IdFTPServer1MakeDirectory(ASender: TIdFTPServerContext;
      var VDirectory: string);
    procedure IdFTPServer1RetrieveFile(ASender: TIdFTPServerContext;
      const AFileName: string; var VStream: TStream);
    procedure IdFTPServer1GetFileSize(ASender: TIdFTPServerContext;
      const AFilename: string; var VFileSize: Int64);
    procedure IdFTPServer1StoreFile(ASender: TIdFTPServerContext;
      const AFileName: string; AAppend: Boolean; var VStream: TStream);
    procedure IdFTPServer1ListDirectory(ASender: TIdFTPServerContext;
      const APath: string; ADirectoryListing: TIdFTPListOutput; const ACmd,
      ASwitches: string);
    procedure IdFTPServer1DeleteFile(ASender: TIdFTPServerContext;
      const APathName: string);
    procedure IdFTPServer1ChangeDirectory(ASender: TIdFTPServerContext;
      var VDirectory: string);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    function ZamienZnaki(APath: String): String;
    function RozmiarPliku(AFile : String) : Integer;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Folder      : String;

implementation
{$R *.DFM}

function TForm1.ZamienZnaki(APath:String):String;
var
 s:string;
begin
  s := StringReplace(APath, '/', '\', [rfReplaceAll]);
  s := StringReplace(s, '\\', '\', [rfReplaceAll]);
  Result := s;
end;

function TForm1.RozmiarPliku(AFile : String) : Integer;
var
 FStream : TFileStream;
begin
Try
 FStream := TFileStream.Create(AFile, fmOpenRead);
 Try
  Result := FStream.Size;
 Finally
  FreeAndNil(FStream);
 End;
Except
 Result := 0;
End;
end;

procedure TForm1.IdFTPServer1ChangeDirectory(
  ASender: TIdFTPServerContext; var VDirectory: string);
begin
  ASender.CurrentDir := VDirectory;
end;

procedure TForm1.IdFTPServer1DeleteFile(ASender: TIdFTPServerContext;
  const APathName: string);
begin
  DeleteFile(ZamienZnaki(Folder+ASender.CurrentDir+'\'+APathname));
end;

procedure TForm1.IdFTPServer1ListDirectory(ASender: TIdFTPServerContext;
  const APath: string; ADirectoryListing: TIdFTPListOutput; const ACmd,
  ASwitches: string);
var
 LFTPItem :TIdFTPListItem;
 SR : TSearchRec;
 SRI : Integer;
begin
  ADirectoryListing.DirFormat := doUnix;
  SRI := FindFirst(Folder + APath + '\*.*', faAnyFile - faHidden - faSysFile, SR);
  While SRI = 0 do
  begin
    LFTPItem := ADirectoryListing.Add;
    LFTPItem.FileName := SR.Name;
    LFTPItem.Size := SR.Size;
    LFTPItem.ModifiedDate := FileDateToDateTime(SR.Time);
    if SR.Attr = faDirectory then
     LFTPItem.ItemType   := ditDirectory
    else
     LFTPItem.ItemType   := ditFile;
    SRI := FindNext(SR);
  end;
  FindClose(SR);
  SetCurrentDir(Folder + APath + '\..');
end;

procedure TForm1.IdFTPServer1StoreFile(ASender: TIdFTPServerContext;
  const AFileName: string; AAppend: Boolean; var VStream: TStream);
begin
 if not Aappend then
   VStream := TFileStream.Create(ZamienZnaki(Folder+AFilename),fmCreate)
 else
   VStream := TFileStream.Create(ZamienZnaki(Folder+AFilename),fmOpenWrite)
end;

procedure TForm1.IdFTPServer1GetFileSize(ASender: TIdFTPServerContext;
  const AFilename: string; var VFileSize: Int64);
Var
 LFile : String;
begin
 LFile := ZamienZnaki( Folder + AFilename );
 try
 If FileExists(LFile) then
   VFileSize :=  RozmiarPliku(LFile)
 else
   VFileSize := 0;
 except
   VFileSize := 0;
 end;
end;

procedure TForm1.IdFTPServer1RetrieveFile(ASender: TIdFTPServerContext;
  const AFileName: string; var VStream: TStream);
begin
  VStream := TFileStream.Create(ZamienZnaki(Folder+AFilename),fmOpenRead);
end;

procedure TForm1.IdFTPServer1MakeDirectory(ASender: TIdFTPServerContext;
  var VDirectory: string);
begin
  if not ForceDirectories(ZamienZnaki(Folder + VDirectory)) then
  begin
    Raise Exception.Create('Nie mozna utworzyc katalogu');
  end;
end;

procedure TForm1.IdFTPServer1UserLogin(ASender: TIdFTPServerContext;
  const AUsername, APassword: string; var AAuthenticated: Boolean);
begin
  if (AUsername='maniek') and (APassword='pankoski') then begin
   AAuthenticated := True;
 end
 else begin
  AAuthenticated := False;
 end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Folder:=Edit1.Text;
idFTPServer1.Active:=True;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
IdFtpServer1.Active:=False;
end;

end.

No to koniec gotowca. Mam nadzieje że to się komuś przyda. Pozdro

12 komentarzy

Baaaardzo dawno tutaj nie bylem, jakie zmiany w gotowcu? Pobierznie z tela zerknalem, odnosnie userow tylko zmiany?

jak by ktoś miał problem sprawdźcie czy nie macie załączonego innego serwera ftp

macie problemy ludzie... przerobienie tego kodu tak zeby dzialal na indy 9 to 5 minut bo roznia sie tylko nazwy zmiennych :]

To, że większość woli INDY 9 to nie znaczy, że źle z tym artykułem.

Raczej słabo, z tym artykułem, większość zdecydowanie woli indy 9, a tam zrobić serwerek jest trochę trudniej

Ja napisałem serwer, a ty musisz mieć klienta ziom. Oto adres http://www.indyproject.org/DemoDownloads/Indy_10_FTPClient.zip
Pozdro

ja mam indy10, kompiluje i co dalej??? moze ktos wyjasni mnie obsluge?
http://forum.ks-ekspert.pl/index.php?showtopic=51174

Jak będzie czas to zrobie multiuser.

Dobre,dobre al tylko dla jednego user'a. Zmienna Folder zawiera aktulaną ścieżkę, ale ta zmienna ma taką samą wartość dla wszytskich user'ów. Zamiast tego lepiej użyć ASender.CurrentDir

Na INDY 9 zadziała będzie tylko inny typ parametru ASender, tzn. ASender: TIdFTPServerThread. Ale poza tym wszytsko tak samo

Na INDY 9 raczej nie zadziała :(

Długie i dobre :P
Tylko ciekawe czy zadziala na indy 9 - bo jednak 9 chyba ma wiecej osob :P
Zreszta nie wazne fajnie jest :)