PHP 프로그래밍 언어를 사용하여 SFTP에 연결하고, 파일을 리스트업하며, 업로드 및 다운로드하는 방법을 배웁니다.

SFTP는 데이터 및 파일의 안전한 전송 및 공유를 위한 표준적이고 안전한 프로토콜입니다. 어떤 경우라도, 프로그래밍 방식으로 SFTP 서버에 연결하는 것은 쉽지 않습니다. 이 기사에서는 본격적인 PHP 기반의 SFTP 클라이언트를 만드는 법을 단계별로 알려드리며, 이 가이드의 마지막에는 스스로 설정을 문제없이 할 수 있게 될 것입니다.

필수사항

먼저 연결할 SFTP 서버가 필요합니다. 아직 가지고 있지 않으시다면 SFTP To Go에서 30초 만에 SFTP 엔드포인트를 취득하실 수 있습니다.

SFTP To Go with maximum security and reliability
SFTP To Go offers managed SFTP/FTPS/S3 as a service - maximum reliability, security, availability, with 1 minute setup. Great for companies of any size, any scale.
Try SFTP To Go now!

PHP에는 SFTP 라이브러리가 처음부터 포함되어 있지 않으므로 PECL (PHP Extension Community Library)을 사용하여 필요한 라이브러리인 ssh2를 설치해야 합니다. 이 라이브러리는 libssh2에 의존하고 있으므로, 미리 의존 라이브러리를 기기에 인스톨 해 둘 필요가 있습니다. 예를 들어, Ubuntu 기기에는 다음을 실행합니다.

$ apt-get install libssh2-1-dev
$ pecl install -a ssh2-1.3

12 factor-app 클라우드 개발 기술에 따라 다음 composer.json과 같이 앱 종속성을 명확하게 정의해야 합니다.

{
  "require": {
    "ext-ssh2": "*"
  }
}

정말 재미있는 것은 지금부터입니다!

SFTP 연결 생성 및 삭제

우선 첫 단계는 새 클래스를 만드는 것부터입니다. 여기에는 SFTP 클라이언트에 필요한 기능이 들어 있습니다. 최초로 추가하는 public 메소드는, 패스워드 인증 또는 키 인증 중에 하나를 사용하여 서버에 접속한 후에, 기존의 세션을 끊는 방법을 제공합니다.

<?php
  class SFTPClient
  {
    private $connection;
    private $sftp;

    public function __construct($host, $port=22)
    {
      $this->connection = @ssh2_connect($host, $port);
      if (! $this->connection)
        throw new Exception("Failed to connect to ${host} on port ${port}.");
    }

    // Login with user and password
    public function auth_password($username, $password)
    {
      if (! @ssh2_auth_password($this->connection, $username, $password))
        throw new Exception("Failed to authenticate with username $username " .
                            "and password.");

      $this->sftp = @ssh2_sftp($this->connection);
      if (! $this->sftp)
        throw new Exception("Could not initialize SFTP subsystem.");
    }

    // Disconnect session
    public function disconnect()
    {
      @ssh2_disconnect($this->connection);
    }
  }

?>
connect-class.php

새로운 메소드를 사용하려면 메인 함수에서 클래스를 인스턴스화하고 auth_password 함수를 호출하여 연결을 시작하고 즉시 disconnect 함수로 연결을 닫습니다. 다음과 같은 URI 형식의 환경 변수 SFTPTOGO_URL을 사용하여 SFTP 서버에 연결하는 데 필요한 모든 정보를 검색합니다: sftp://user:password@host. 해당 변수는 함수 내에서 분석(파싱)되고 사용자 이름, 암호, 호스트 및 포트가 추출됩니다.

<?php
  function main()
  {
    $raw_url = getenv('SFTPTOGO_URL');

    // Parse URL
    $parsed_url = parse_url($raw_url);

    if($parsed_url === false)
    {
      fwrite(STDERR, "Failed to parse SFTP To Go URL.\n");
      exit(1);
      
    }

    // Get user name and password
    $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : null;
    $pass = isset( $parsed_url['pass'] ) ? $parsed_url['pass'] : null;

    // Parse Host and Port
    $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : null;

    // Port is always 22
    $port = isset( $parsed_url['port'] ) ? $parsed_url['port'] : 22;

    fwrite(STDOUT, "Connecting to [${host}] ...\n");
    $client = new SFTPClient($host, $port);
    $client->auth_password($user, $pass);

    fwrite(STDOUT, "Disconnecting from [${host}] ...\n");
    $client->disconnect();

  }

  main();
?>
connect-main.php

파일 리스트 표시

연결이 성공하면 클래스에 원격 SFTP 서버의 파일을 나열하는 데 사용되는 함수를 추가합니다. listFiles 함수는 $remote_dir 인수를 취해 파일명의 배열과 그 사이즈, 갱신 시각을 돌려줍니다. 이 함수를 호출하려면, 단순히 패스를 건네주고 ( . 로 현재 디렉토리의 내용을 모두 열람할 수 있습니다), 취득한 배열을 처리합니다.

<?php
  // List remote directory files
  function listFiles($remote_dir) {
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_dir);
    $dir = "ssh2.sftp://$sftp$realpath";
    $tempArray = array();

    fwrite(STDOUT, "Listing [${remote_dir}] ...\n\n");

    if (is_dir($dir)) {
      if ($dh = opendir($dir)) {
        while (($file = readdir($dh)) !== false) {
          $realpath = ssh2_sftp_realpath($sftp, $file);
          $filetype = filetype($dir . $realpath);
          $modTime = "";
          $size = sprintf("%.2f", filesize($dir . $realpath));

          if($filetype == "dir") {
            $file = $file . "/";
            $modTime = "";
            $size = "PRE";
          }

          $tempArray[] = $file;

          printf("%19s %12s %s\n", $modTime, $size, $file);
        }
        closedir($dh);
      }
    }
    return $tempArray;
  }

?>
listfiles.php

파일 업로드

이제 파일을 업로드 해봅시다. uploadFile 함수를 사용하여 두 개의 인수를 전달합니다. 그리고 함수 호출은 다음과 같습니다: $client->uploadFile("./local.txt", "./remote.txt");

<?php
  // Upload local file to remote file
  public function uploadFile($local_file, $remote_file)
  {
    fwrite(STDOUT, "Uploading [${local_file}] to [${remote_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $stream = @fopen("ssh2.sftp://$sftp$realpath", 'w');
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $data_to_send = @file_get_contents($local_file);
    if ($data_to_send === false)
      throw new Exception("Could not open local file: $local_file.");
    if (@fwrite($stream, $data_to_send) === false)
      throw new Exception("Could not send data from file: $local_file.");
    @fclose($stream);
  }
?>
uploadfile.php

파일 다운로드

마지막으로 중요한 한 가지, 파일을 다운로드하려면 downloadFile함수를 사용합니다. 리모트 파일에 대한 경로와 다운로드한 파일을 저장하는 로컬 경로를 함수에 전달합니다. 이 함수는 다음을 통해 호출하게 됩니다: $client->downloadFile("./remote.txt", "./download.txt");

<?php
  // Upload local file to remote file
  public function uploadFile($local_file, $remote_file)
  {
    fwrite(STDOUT, "Uploading [${local_file}] to [${remote_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $stream = @fopen("ssh2.sftp://$sftp$realpath", 'w');
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $data_to_send = @file_get_contents($local_file);
    if ($data_to_send === false)
      throw new Exception("Could not open local file: $local_file.");
    if (@fwrite($stream, $data_to_send) === false)
      throw new Exception("Could not send data from file: $local_file.");
    @fclose($stream);
  }
?>
uploadfile.php
SFTP To Go with maximum security and reliability
SFTP To Go offers managed SFTP/FTPS/S3 as a service - maximum reliability, security, availability, with 1 minute setup. Great for companies of any size, any scale.
Check out SFTP To Go!

마무리

이제 끝입니다! 이 프로그램을 처음부터 끝까지 실행하고 싶다면 아래 코드를 복사하여 main.php로 저장하십시오:

<?php

class SFTPClient
{
  private $connection;
  private $sftp;

  public function __construct($host, $port=22)
  {
    $this->connection = @ssh2_connect($host, $port);
    if (! $this->connection)
      throw new Exception("Failed to connect to ${host} on port ${port}.");
  }

  // Login with user and password
  public function auth_password($username, $password)
  {
    if (! @ssh2_auth_password($this->connection, $username, $password))
      throw new Exception("Failed to authenticate with username $username " .
                          "and password.");

    $this->sftp = @ssh2_sftp($this->connection);
    if (! $this->sftp)
      throw new Exception("Could not initialize SFTP subsystem.");
  }

  // Login with SSH agent
  public function auth_agent($username)
  {
    if (! @ssh2_auth_agent($this->connection, $username))
      throw new Exception("Failed to authenticate with username $username " .
                          "and public key.");

    $this->sftp = @ssh2_sftp($this->connection);
    if (! $this->sftp)
      throw new Exception("Could not initialize SFTP subsystem.");
  }

  // Disconnect session
  public function disconnect()
  {
    @ssh2_disconnect($this->connection);
  }
  
  // List remote directory files
  function listFiles($remote_dir) {
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_dir);
    $dir = "ssh2.sftp://$sftp$realpath";
    $tempArray = array();

    fwrite(STDOUT, "Listing [${remote_dir}] ...\n\n");

    if (is_dir($dir)) {
      if ($dh = opendir($dir)) {
        while (($file = readdir($dh)) !== false) {
          $realpath = ssh2_sftp_realpath($sftp, $file);
          $filetype = filetype($dir . $realpath);
          $modTime = "";
          $size = sprintf("%.2f", filesize($dir . $realpath));

          if($filetype == "dir") {
            $file = $file . "/";
            $modTime = "";
            $size = "PRE";
          }

          $tempArray[] = $file;

          printf("%19s %12s %s\n", $modTime, $size, $file);
        }
        closedir($dh);
      }
    }

    return $tempArray;
  }

  // Upload local file to remote file
  public function uploadFile($local_file, $remote_file)
  {
    fwrite(STDOUT, "Uploading [${local_file}] to [${remote_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $stream = @fopen("ssh2.sftp://$sftp$realpath", 'w');
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $data_to_send = @file_get_contents($local_file);
    if ($data_to_send === false)
      throw new Exception("Could not open local file: $local_file.");
    if (@fwrite($stream, $data_to_send) === false)
      throw new Exception("Could not send data from file: $local_file.");
    @fclose($stream);
  }

  // Download remote file to local file
  public function downloadFile($remote_file, $local_file)
  {
    fwrite(STDOUT, "Downloading [${remote_file}] to [${local_file}] ...\n");

    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    $contents = stream_get_contents($stream);
    if (! $stream)
      throw new Exception("Could not open file: $realpath");
    $contents = fread($stream, filesize("ssh2.sftp://$sftp$realpath"));
    file_put_contents ($local_file, $contents);
    @fclose($stream);
  }

  // Delete remote file
  public function deleteFile($remote_file){
    fwrite(STDOUT, "Deleting [${remote_file}] ...\n");
    $sftp = $this->sftp;
    $realpath = ssh2_sftp_realpath($sftp, $remote_file);
    unlink("ssh2.sftp://$sftp$realpath");
  }
}

function main()
{
  $raw_url = getenv('SFTPTOGO_URL');

  // Parse URL
  $parsed_url = parse_url($raw_url);

  if($parsed_url === false)
  {
    fwrite(STDERR, "Failed to parse SFTP To Go URL.\n");
    exit(1);
  }

  // Get user name and password
  $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : null;
  $pass = isset( $parsed_url['pass'] ) ? $parsed_url['pass'] : null;

  // Parse Host and Port
  $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : null;

  // Port is always 22
  $port = isset( $parsed_url['port'] ) ? $parsed_url['port'] : 22;

  fwrite(STDOUT, "Connecting to [${host}] ...\n");

  try
  {
    $client = new SFTPClient($host, $port);
    $client->auth_password($user, $pass);

    //*
    //* List working directory files
    //*
    $client->listFiles(".");

    //*
    //* Upload local file to remote file
    //*
    $client->uploadFile("./local.txt", "./remote.txt");

    //*
    //* Download remote file to local file
    //*
    $client->downloadFile("./remote.txt", "./download.txt");

    //*
    //* Delete remote file
    //*
    $client->deleteFile("./remote.txt");
  }
  catch (Exception $e)
  {
    echo $e->getMessage() . "\n";
  }
}

main();

?>
main.php

코드를 실행하기 위해서는, 아래의 명령을 이용하세요:

php main.php

또한, Github에 있는 저희 리포지토리를 복제해서 활용하거나, 다른 샘플들을 체크해 보세요.