0

ほぼ 2 日間のグーグル検索と、ウェブ全体で見つけたいくつかの異なる可能性を試した後、最終的に答えが得られることを期待して、ここでこの質問をしています。

まず、私がやりたいことは次のとおりです。

単一サーバー上の複数のクライアント間で大量の大きなファイルを交換する目的で、クライアントとサーバー アプリケーションを開発しています。クライアントは純粋なJava (JDK 1.6) で開発され、Web アプリケーションはGrails (2.0.0) で開発されています。

クライアントの目的は、ユーザーが大量の大きなファイル (通常はそれぞれ約 2GB) を交換できるようにすることであるため、アップロードを再開できるように実装する必要があります。つまり、ユーザーがアップロードを停止および再開できるようにする必要があります。いつでも。

これが私これまでにしたことです:

私は実際にやりたいことをやり、大きなファイルをサーバーにストリーミングすることができましたが、生のソケットを使用してアップロードを一時停止および再開することもできました。サーバーに通常のリクエストを (Apache の HttpClient ライブラリを使用して) 送信し、サーバーから自由に使用できるポートを送信してもらい、サーバーで ServerSocket を開き、クライアントからその特定のソケットに接続します。

ここに問題があります:

実際、これには少なくとも 2 つの問題があります。

  1. これらのポートは自分で開くので、開いているポートと使用中のポートを自分で管理する必要があります。これは非常にエラーが発生しやすいです。
  2. 私は実際、膨大な量の (同時) 接続を管理する Grails の機能を回避しています。

最後に、私が今やるべきことと問題は次のとおりです。

上で述べた問題は容認できないので、Grails にこだわりながら、Java の URLConnection/HttpURLConnection クラスを使用することになっています。

サーバーへの接続と単純なリクエストの送信はまったく問題なく、すべて正常に機能しました。ストリーム (クライアントでは接続の OutputStream、サーバーでは要求の InputStream) を使用しようとしたときに問題が発生しました。クライアントの OutputStream を開いてデータを書き込むのは、とても簡単です。しかし、リクエストの InputStream からの読み取りは、そのストリームが常に emptyであるように見えるため、私には不可能に思えます。

サンプルコード

サーバー側(Groovy コントローラー)の例を次に示します。

def test() {
    InputStream inStream = request.inputStream
    
    if(inStream != null) {
        int read = 0;
        byte[] buffer = new byte[4096];
        long total = 0;
        
        println "Start reading"
        
        while((read = inStream.read(buffer)) != -1) {
            println "Read " + read + " bytes from input stream buffer"      //<-- this is NEVER called
        }       
        
        println "Reading finished"
        println "Read a total of " + total + " bytes"   // <-- 'total' will always be 0 (zero)
    } else {
        println "Input Stream is null"      // <-- This is NEVER called
    }
}

これは私がクライアント側(Javaクラス)で行ったことです:

public void connect() {
    final URL url = new URL("myserveraddress");
    final byte[] message = "someMessage".getBytes();        // Any byte[] - will be a file one day
    HttpURLConnection connection = url.openConnection();
    connection.setRequestMethod("GET");                     // other methods - same result
    
    // Write message 
    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
    out.writeBytes(message);
    out.flush();
    out.close();
    
    // Actually connect
    connection.connect();                                   // is this placed correctly?
    
    // Get response
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    
    String line = null;
    while((line = in.readLine()) != null) {
        System.out.println(line);                   // Prints the whole server response as expected
    }
    
    in.close();
}

前述したように、問題はrequest.inputStream常に空の InputStream が生成されるため、そこから何も読み取れないことです (もちろん)。しかし、それはまさに私がやろうとしていることなので(ファイルをストリーミングしてサーバーにアップロードし、InputStream から読み取ってファイルに保存することができます)、これはかなり残念です。

さまざまな HTTP メソッドさまざまなデータ ペイロードを試し、コードを何度も再配置しましたが、問題を解決できなかったようです。

私が見つけたいもの

もちろん、私の問題の解決策を見つけたいと思っています。ヒント、コード スニペット、ライブラリの提案など、何でも大歓迎です。たぶん、私はそれをすべて間違っていて、まったく別の方向に進む必要があるのか​​もしれません.

では、サーバー側でポートを手動で開かずに、Java クライアントから Grails Web アプリケーションへのかなり大きな (バイナリ) ファイルの再開可能なファイル アップロードを実装するにはどうすればよいでしょうか?

4

2 に答える 2

0

HTTP GETメソッドには、範囲を取得するための特別なヘッダーがあります。http ://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35ほとんどのダウンローダーは、サーバーから再開可能なダウンロードを行うために使用します。

私が理解しているように、POST / PUTリクエストにこのヘッダーを使用するための標準的な方法はありませんが、それはあなた次第ですよね?のようなヘッダーを使用して、標準のhttpアップロードを受け入れるかなり標準的なGrailsコントローラーを作成できますRange: bytes=500-999。そして、コントローラーは、クライアントからアップロードされたこの500バイトを、500の位置からファイルに入れる必要があります。

この場合、ソケットを開いたり、独自のプロトコルを作成したりする必要はありません。

PS 500バイトは単なる例であり、おそらくはるかに大きなパーツを使用しています。

于 2012-07-06T15:36:31.987 に答える
0

クライアント側 Java プログラミング:

    public class NonFormFileUploader {
    static final String UPLOAD_URL= "http://localhost:8080/v2/mobileApp/fileUploadForEOL";


    static final int BUFFER_SIZE = 4096;

    public static void main(String[] args) throws IOException {
        // takes file path from first program's argument
        String filePath = "G:/study/GettingStartedwithGrailsFinalInfoQ.pdf";
        File uploadFile = new File(filePath);

        System.out.println("File to upload: " + filePath);

        // creates a HTTP connection
        URL url = new URL(UPLOAD_URL);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setDoOutput(true);
        httpConn.setRequestMethod("POST");
        // sets file name as a HTTP header
        httpConn.setRequestProperty("fileName", uploadFile.getName());

        // opens output stream of the HTTP connection for writing data
        OutputStream outputStream = httpConn.getOutputStream();

        // Opens input stream of the file for reading data
        FileInputStream inputStream = new FileInputStream(uploadFile);

        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = -1;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            System.out.println("bytesRead:"+bytesRead);
            outputStream.write(buffer, 0, bytesRead);
            outputStream.flush();
        }

        System.out.println("Data was written.");
        outputStream.flush();
        outputStream.close();
        inputStream.close();

        int responseCode = httpConn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // reads server's response
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    httpConn.getInputStream()));
            String response = reader.readLine();
            System.out.println("Server's response: " + response);
        } else {
            System.out.println("Server returned non-OK code: " + responseCode);
        }
    }
 }

サーバー側 Grails プログラム:

コントローラーの内部:

def fileUploadForEOL(){
    def result
    try{
        result = mobileAppService.fileUploadForEOL(request);
    }catch(Exception e){
        log.error "Exception in fileUploadForEOL service",e
    }
    render result as JSON
}

サービス クラスの内部:

    def fileUploadForEOL(request){
    def status = false;
    int code = 500
    def map = [:]
    try{
    String fileName = request.getHeader("fileName");
    File saveFile = new File(SAVE_DIR + fileName);

    System.out.println("===== Begin headers =====");
    Enumeration<String> names = request.getHeaderNames();
    while (names.hasMoreElements()) {
        String headerName = names.nextElement();
        System.out.println(headerName + " = " + request.getHeader(headerName));        
    }
    System.out.println("===== End headers =====\n");

    // opens input stream of the request for reading data
    InputStream inputStream = request.getInputStream();

    // opens an output stream for writing file
    FileOutputStream outputStream = new FileOutputStream(saveFile);

    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = inputStream.read(buffer);

    long count =  bytesRead

    while(bytesRead != -1) {
        outputStream.write(buffer, 0, bytesRead);
      bytesRead = inputStream.read(buffer);
      count += bytesRead
    }
     println "count:"+count
    System.out.println("Data received.");
    outputStream.close();
    inputStream.close();

    System.out.println("File written to: " + saveFile.getAbsolutePath());

    code = 200

    }catch(Exception e){
        mLogger.log(java.util.logging.Level.SEVERE,"Exception in fileUploadForEOL",e);
    }finally{
        map <<["code":code]
    }
    return map
}

上記のコードを試してみましたが、うまくいきました(ファイルサイズが3〜4MBの場合のみですが、小さなサイズのファイルの場合、コードの一部のバイトが欠落しているか、来ていませんが、リクエストヘッダーのコンテンツ長が来ていますが、なぜそれが起こっているのかわかりません.)

于 2015-11-19T13:09:39.287 に答える