ファイルアップロードサンプル(using JSON with JAX-RS and JAXB)

前回2015-11-01 - tomoTakaの日記とは、違ってファイルをJSON形式でアップしてみたかったのちょっと実装してみました。
クライアント側(JavaScript)で選択したファイルを読み込んで、Base64encodeした文字列をサーバにXMLHttpRequestを使って送信。クライアントとサーバ側のJSONデータのバインドは「@XmlRootElement」を「FileInfo.java」クラスにつけて簡単に実現できました。そもそもこの方法がどうかは別として、とにかく動作してので嬉しいです!

画面

  • アップに成功

選択したファイルを指定した場所の保存

  • アップに失敗

選択したファイルが大きすぎて読み込みでタイムアウト発生した場合

クライアント側

  • fileupload.html
<h3>file upload (json)</h3>
  <input type="file" id="fileJson">
  <label class="label-info">Destination:</label>
  <input type="text" value="/tmp" id="destinationJson">
  <button class="btn btn-primary" id="uploadBtn">upload</button>
  <output id="uploadMsg"></output>
  • fileupload.js
'use strict';
(function(){
    // ファイルアップ(json)
    var files = [];
    // アップ対象のファイルを取得
    var fileJson = document.getElementById('fileJson');
    fileJson.addEventListener('change', function(){
        files = this.files;
    });
    // uploadボタンをクリック時
    var upbtn = document.getElementById('uploadBtn');
    upbtn.addEventListener('click', function(){
        if (files.length === 0) {
            var msg = document.getElementById('uploadMsg');
            msg.innerHTML = 'ファイルを選択してください';
            msg.classList.add('label-warning');
            return;
        }
        // 選択したファイルを読み込み
        var file = files[0];
        var reader = new FileReader();
        var p = new Promise(function(resolve, reject){
            // 1秒以上かかった場合、エラーとする
            window.setTimeout(function(){
                reject('timeout');
            }, 1000);
            reader.onload = (function(){
                return function(e){
                // 「data:text/plain;base64,xxxxx」がresult、なのでカンマで区切って内容を取得
                var fileBase64 = e.target.result.split(',')[1];
                resolve(fileBase64);
            };
            })();
        });
        // 選択したファイルを読込む
        reader.readAsDataURL(file);
        // サーバに非同期通信
        var XHR = new XMLHttpRequest();
        XHR.open('POST', '/FileUploadWeb/webresources/upload');
        XHR.setRequestHeader('Content-Type', 'application/json; charset="UTF-8"');
        XHR.addEventListener('load', function(e){
            var msg = document.getElementById('uploadMsg');
            msg.innerHTML =  XHR.responseText;
            msg.classList.add('label-success');
        });
        p.then(
            function(fileBase64){
                var destination = document.getElementById('destinationJson').value;
                var data = {
                    'destination' : destination,
                    'fileName' : file.name,
                    'fileType' : file.type,
                    'file' : fileBase64
                };
                // ファイル読み込みが終了してから、通信開始
                XHR.send(JSON.stringify(data));
            },
            function(errMsg){
                var msg = document.getElementById('uploadMsg');
                msg.innerHTML = errMsg;
                msg.classList.add('label-warning');
        });
    });
}());

サーバ側

JavaEEのドキュメント「using JSON with JAX-RS and JAXB」の箇所を参考に実装。

@XmlRootElement
public class UploadInfo {
    String destination;
    String fileName;
    String fileType;
    String file;
    public String getDestination() {
        return destination;
    }
    public void setDestination(String destination) {
        this.destination = destination;
    }
...
  • ApplicationConfig.java

UploadResource.javaを実装している時に「@javax.ws.rs.Path」アノテーションを指定するとNetBeansが自動でこのクラスを作成。

@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new java.util.HashSet<>();
        addRestResourceClasses(resources);
        return resources;
    }
    private void addRestResourceClasses(Set<Class<?>> resources) {
        resources.add(UploadResource.class);
    }
  • UploadResource.java

サーバ側でのアップされたファイルの保存処理。「@Consumes(MediaType.APPLICATION_JSON)」を指定して引数に上記で作成した「UploadInfo」を指定することで、クライアントで送信した情報を取得。

@javax.ws.rs.Path("/upload")
public class UploadResource {
    
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String uploadFile(UploadInfo info) throws IOException{
        String returnMsg = "";
        String fileType = info.getFileType();
        Path path = Paths.get(info.getDestination() + File.separator 
                + info.getFileName());
        if (fileType.equals("text/plain")){
            // ファイルに保存
            saveFile(path, info.getFile());
            returnMsg = String.format("%sに保存しました", path.toAbsolutePath().toString());
        } else if(fileType.startsWith("image")) {
            // イメージをして保存
            saveImage(path, info.getFile());
            returnMsg = String.format("%sに保存しました", path.toAbsolutePath().toString());
        } else {
            // 保存エラー
            returnMsg = "保存に失敗しました。";
        }
        return returnMsg;
    }

    private void saveFile(Path path, String base64String)
            throws IOException {
        Files.deleteIfExists(path);
        Base64.Decoder decoder = Base64.getDecoder();
        byte[] decode = decoder.decode(base64String);
        try (BufferedWriter writer = Files.newBufferedWriter(path, 
                StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW);){
            writer.write(new String(decode));
        } 
    }

    private void saveImage(Path path, String base64String) throws IOException {
        Files.deleteIfExists(path);
        byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(base64String);
        try (FileOutputStream x = new FileOutputStream(path.toFile());){
            x.write(bytes);
        }
    }
}

プロジェクトはNetBeansで作成してここtomoTaka01/FileUploadWeb · GitHubにアップ。
まだまだ勉強不足です、、、