今作っているWebアプリケーションでサーバのログファイルをダウンロードしたいという要件があり、そこでganymedを使ってみることにしました。アプリケーションをデプロイしているサーバとは別に複数存在するサーバ上にあるログファイルを画面から選択してクリックでダウンロードという流れです。
なお、サーバとログファイルが格納されているディレクトリ情報は別途DB等から取る形にして、下記はSFTPでファイルを取得する箇所の抜粋です。
POM.xml ganymed指定
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
</dependency>
画面に表示するログファイル情報
public class LogFile {
/** ファイル名 */
private String fileName;
/** 更新日時 */
private String upDateTime;
/** ディレクトリ */
private String directory;
/** サイズ */
private long fileSize;
/** ホスト名 */
private String hostName;
以下、ゲッタとセッタは割愛
SFTP処理
※サーバは一般的なID・パスワード認証です。
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に直ファイルを突っ込むので、一時ファイルを置いたりしません。
/**
* ログダウンロード画面
* */
@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
※ディレクトリリストの箇所は、上記ソース上割愛しています。
ディレクトリリストの左に置いたボタンクリックで、ファイル一覧を取得し、ファイル一覧のファイル名クリックでダウンロードします。
<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でエラーになりちょっとハマりました。。