[Ruby on Rails]FTPにて正しくファイルが取得できたかを確認する – MD5による検証

2014.07.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

今回はFTPサーバより取得したファイルが、正しくダウンロードできているのか検証する方法についてです。結論から書くとMD5チェックサムを使い、FTPサーバ上に用意したチェックサムと、ダウンロードしたファイルから算出したMD5値が一致するかで検証します。まあ、昔からある非常に古典的なやり方です。

処理概要

タイトルにもある通り、今回のサンプルはRuby on Railsを使用しました。処理の概要としては、以下の通りとなります。

  • FTPサーバ内には、ダウンロード対象のファイルと、そのファイルより算出したMD5の値(チェックサム)を記述したファイルを用意する。
  • Railsアプリは、FTPサーバに用意したファイルを取得する。
  • 取得後、ダウンロードしたファイルから算出したMD5の値と、チェックサムを記述したファイル内の値を比較する。

FTPサーバ

では、FTPサーバで行う手順についてです。FTPサーバ内にダウンロード対象のファイルを用意した後、チェックサムを記述したファイルを用意します。

まずFTPサーバを起動します。Macの場合は以下のコマンドとなります。

$ sudo -s launchctl load -w /System/Library/LaunchDaemons/ftp.plist

FTPサーバの任意のフォルダに、ダウンロードしたファイルを用意します。今回は「sample1.dmg」「sample2.dmg」の2ファイルを用意しました。

その後、以下のコマンドでファイルのMD5を算出し、チェックサムを記述したファイルを用意します。

$ md5 * >> large.md5sum

「md5」コマンドは、Macの場合のコマンドです。「*」を指定することで、フォルダ内の全ファイルのMD5を算出しています。パイプで、算出した結果をファイルに出力しています。この結果、以下のようなチェックサムが出力されました。

MD5 (sample1.dmg) = 2995f70a1779dc28b97be7f4b98f0719
MD5 (sample2.dmg) = 4796db9cc3a928cf477f6c3f1cde0ea4

見ての通り「MD5 (ファイル名) = MD5値」という形式で出力されています。

サンプルソース

最後に、Railsでのサンプルソースです。一番上の「self.exec」メソッドが、エントリーポイントとなります。
尚、各パスはsettingslogicを使いymlファイル内に定義しているため、「Settings〜」という記述となっています。

require 'net/ftp'
require 'digest/md5'

class FtpMd5
  def self.exec
    download
    check_md5
  end

  private

  def self.check_md5
    md5sum = read_md5sum

    Dir.glob(download_folder + "/*.dmg") {|file|
      download_md5 = Digest::MD5.hexdigest(File.open(file, 'rb').read)
      original_md5 = md5sum[File.basename(file)]
      return false if download_md5 != original_md5
    }

    true
  end

  def self.read_md5sum
    result = Hash.new
    reg = Regexp.new('MD5 \((.*)\)')
    File.open(File.join(download_folder, Settings.download.md5sum_file), "r") {|file|
      file.each {|line|
        content = line.split(' = ')
        match = reg.match(content[0])
        result[match[1]] = content[1].chomp
      }
    }
    result
  end

  def self.download
    Net::FTP.open(Settings.download.host) do |ftp|
      ftp.login(Settings.download.user, Settings.download.password)
      ftp.passive = true
      ftp.chdir(Settings.download.remote_large_folder)
      ftp.list('*.dmg').each {|file|
        file_name = file.match(/[^\s]*\.dmg/).to_s
        ftp.getbinaryfile(file_name, File.join(download_folder, file_name))
      }
      ftp.gettextfile(Settings.download.md5sum_file, File.join(download_folder, Settings.download.md5sum_file))
    end
  end

  def self.download_folder
    File.join(Rails.root, Settings.download.local_folder)
  end
end

「self.exec」メソッド内では最初に「download」メソッドを呼び、FTPサーバよりファイルをダウンロードしています。次に「check_md5」メソッドを呼び、MD5の比較にて正しくファイルが取得できたかを確認しています。

「download」メソッドでは、特に変わった事は行っておりません。「Net::FTP」を使い、FTPにてファイルをダウンロードしています。.dmgファイルはバイナリなので「getbinaryfile」を(44行目)、MD5チェックサムファイルはテキストなので「gettextfile」(46行目)を使用しています。

「check_md5」メソッドでは「Digest::MD5.hexdigest」を使い、ダウンロードしたファイルの中身からMD5値を生成しています(16行目)。今回は一括でファイルを読み込んでいますが、ファイルの容量が多い場合は分割したほうが良いようです。ちなみに1.2Gのファイルを用いましたが、私の環境では特に問題はなかったです。18行目でダウンロードしたファイルから生成したMD5値と、FTPサーバに予め用意したチェックサムを比較し、異なるようならfalseを返却しています。

まとめ

ダウンロード対象のファイルのチェックサムを予め作っておくことで、ダウンロードが正常に出来たかを確認することができました。今回はダウンロード直後にチェックを行いましたが、さらに別箇所に転送する場合など、案件に合わせて任意のタイミングでチェックすることもできるかと思います。何かのときに、参考になれば幸いです。