今作っているWebアプリケーションでサーバのログファイルをダウンロードしたいという要件があり、そこでganymedを使ってみることにしました。アプリケーションをデプロイしているサーバとは別に複数存在するサーバ上にあるログファイルを画面から選択してクリックでダウンロードという流れです。
なお、サーバとログファイルが格納されているディレクトリ情報は別途DB等から取る形にして、下記はSFTPでファイルを取得する箇所の抜粋です。
POM.xml ganymed指定
1 2 3 4 5 |
<dependency> <groupId>ch.ethz.ganymed</groupId> <artifactId>ganymed-ssh2</artifactId> <version>262</version> </dependency> |
画面に表示するログファイル情報
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class LogFile { /** ファイル名 */ private String fileName; /** 更新日時 */ private String upDateTime; /** ディレクトリ */ private String directory; /** サイズ */ private long fileSize; /** ホスト名 */ private String hostName; 以下、ゲッタとセッタは割愛 |
SFTP処理
※サーバは一般的なID・パスワード認証です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
import ch.ethz.ssh2.Connection; import ch.ethz.ssh2.SFTPv3Client; import ch.ethz.ssh2.SFTPv3DirectoryEntry; import ch.ethz.ssh2.SFTPv3FileHandle; /** * SFTPファイルアクセスクラス by Ganymed SSH-2 * */ @Stateless public class SFTPFileAccess { private final int PORT = 22; private SFTPv3Client sftp = null; private Connection conn = null; /** * リモートホストへログインします * @param HostName リモートホスト名 * @param User ログインユーザー名 * @param Password ログインパスワード * @throws IOException * * */ public boolean loginHost(String HostName, String User, String Password) throws IOException{ conn = new Connection(HostName, PORT); conn.connect(); if (!conn.authenticateWithPassword(User, Password)) { //エラー return false; } return true; } /** * リモートホスト接続をクローズします * @throws IOException * * */ public void logOutHost() throws IOException{ if(conn != null){ conn.close(); } return; } /** * リモートホスト上の指定ディレクトリのファイル一覧を取得する * @param String RemoteDir ディレクトリ名 * @param List<LogFile> セットするファイル一覧 * @throws IOException * */ public void getSFTPList(String RemoteDir, List<LogFile> FileList) throws IOException { if(conn == null){ return; } sftp = new SFTPv3Client(conn); List<SFTPv3DirectoryEntry> DirList = sftp.ls(RemoteDir); if ( FileList == null ){ FileList = new ArrayList<LogFile>(); } for (SFTPv3DirectoryEntry Entry : DirList ){ //ディレクトリ以外 if (!Entry.longEntry.startsWith("d") ){ LogFile file = new LogFile(); file.setDirectory(RemoteDir); file.setFileName(Entry.filename); file.setHostName(conn.getHostname()); file.setFileSize(Entry.attributes.size); //ファイル更新時間 Date tm = new Date(Entry.attributes.mtime * 1000L); file.setUpDateTime(DateUtils.format(tm, DateUtils.FORMAT_YYYY_MM_DD_HH_MM_SS)); FileList.add(file); } } return; } /** * SFTPファイル受信 * @param remoteFile 受信元ファイル * @param stream 出力ストリーム * * */ public void getFile( String remoteFile, OutputStream stream) throws IOException{ if(conn == null){ return; } if(sftp == null){ sftp = new SFTPv3Client(conn); } long remoteFileSize = sftp.stat(remoteFile).size; // open remote file with read only SFTPv3FileHandle sftpFileHandle = sftp.openFileRO(remoteFile); BufferedOutputStream bos = new BufferedOutputStream(stream); byte[] buffer = new byte[1024]; long offset = 0; int readLength = 0; while( (readLength = sftp.read(sftpFileHandle, offset, buffer, 0, buffer.length)) != -1){ bos.write(buffer, 0, readLength); offset += readLength; } // flush & Close bos.flush(); bos.close(); sftp.closeFile(sftpFileHandle); // compare if (offset == remoteFileSize) { } else { System.out.println("failed: file size is different from remote. remote:" + remoteFileSize+", local:"+offset); } return; } } |
画面処理
※HttpServletResponseのOutputStreamに直ファイルを突っ込むので、一時ファイルを置いたりしません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
/** * ログダウンロード画面 * */ @ManagedBean(name="LogDownload") @SessionScoped public class LogDownload implements Serializable { private static final long serialVersionUID = 1L; protected static Logger logger = LogManager.getLogger(); @EJB private SFTPFileAccess ftp; private String selectedServer; private List<LogFile> fileList; public LogDownload() { fileList = new ArrayList<LogFile>(); } public List<LogFile> getFileList() { return fileList; } public void setFileList(List<LogFile> fileList) { this.fileList = fileList; } public String getSelectedServer() { return this.selectedServer; } public void setSelectedServer(String selectedServer) { this.selectedServer = selectedServer; } /** * ディレクトリ選択時にファイル一覧を取得 * */ public void getList(){ try { Map<String,String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); String fileDir = params.get("fileDir"); ftp.loginHost(selectedServer, dao.getServerLoginUser(selectedServer), dao.getServerLoginPasswd(selectedServer)); fileList.clear(); ftp.getSFTPList(fileDir,fileList); } catch (IOException e) { e.printStackTrace(); } } /** * ファイルダウンロード * */ public void getFile(){ Map<String,String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); String fileName = params.get("fileName"); String fileDir = params.get("fileDir"); try { HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance() .getExternalContext().getResponse(); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName); OutputStream a = response.getOutputStream(); ftp.getFile(fileDir +"/" + fileName , a); FacesContext.getCurrentInstance().responseComplete(); } catch (IOException e) { e.printStackTrace(); } } } |
xhtml
※ディレクトリリストの箇所は、上記ソース上割愛しています。
ディレクトリリストの左に置いたボタンクリックで、ファイル一覧を取得し、ファイル一覧のファイル名クリックでダウンロードします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<h:form id="listForm"> <h:dataTable var="dirlist" value="#{LogDownload.directoryList}" rowIndexVar="rowIndex" class="table"> <h:column> <f:facet name="header" >#{words.Label_LogList}</f:facet> <h:commandLink value="#{dirlist.INFO_VALUE}" actionListener="#{LogDownload.downLoad}"> <f:attribute name="directory" value="#{dirlist.INFO_VALUE}"/> </h:commandLink> </h:column> <h:column> <h:commandButton id="listget" action="#{LogDownload.getList}" value="#{words.Label_FileListGet}"> <f:param name="fileDir" value="#{dirlist.INFO_VALUE}" /> <f:ajax render=":listForm:loglist"/> </h:commandButton> </h:column> </h:dataTable> <h:dataTable id="loglist" var="list" value="#{LogDownload.fileList}" styleClass="listTable"> <h:column><f:facet name="header" >ファイル名</f:facet> <h:commandLink id="filelistName" value="#{list.fileName}" action="#{LogDownload.getFile}"> <f:param name="fileName" value="#{list.fileName}" /> <f:param name="fileDir" value="#{list.directory}" /> </h:commandLink> </h:column> <h:column><f:facet name="header">ファイルサイズ</f:facet> <h:outputText value="#{list.fileSize}"> <f:convertNumber groupingUsed="true" /> </h:outputText> </h:column> <h:column><f:facet name="header" >ファイル更新日時</f:facet> <h:outputText value="#{list.upDateTime}"/> </h:column> </h:dataTable> </h:form> |
エラーハンドリングやサーバの切断、ファイルサイズの数値右寄せ等、細かい箇所がまだ出来ていませんが、とりあえず動きそうです。
最初、ログファイル情報のプロパティ名が先頭大文字とかだと、JSFでエラーになりちょっとハマりました。。