可恢复从Java客户端上传到Grails Web应用程序?

时间:2012-07-06 15:12:55

标签: java grails upload inputstream resume-upload

经过近两个工作日的谷歌搜索并尝试了我在整个网络上发现的几种不同的可能性,我在这里提出这个问题,希望我最终能得到答案。

首先,这是我想做的事情

我正在开发一个客户端和一个服务器应用程序,目的是在单个服务器上的多个客户端之间交换大量大型文件。客户端使用纯 Java (JDK 1.6)开发,而Web应用程序则使用 Grails (2.0.0)完成。

由于客户端的目的是允许用户交换大量文件(通常每个大约2GB),我必须以某种方式实现它,以便上传可恢复,即用户可以随时停止和恢复上传。

到目前为止,我做了什么

我实际上设法做了我想做的事情并将大文件流式传输到服务器,同时仍然可以使用原始套接字暂停和恢复上传。我会向服务器发送一个常规请求(使用Apache的HttpClient库)让服务器向我发送一个免费供我使用的端口,然后在服务器上打开一个ServerSocket并从客户端连接到该特定套接字。 / p>

这是的问题

实际上,至少存在两个问题:

  1. 我自己打开这些端口,所以我必须自己管理开放和使用过的端口。这很容易出错。
  2. 我实际上绕过了Grails管理大量(并发)连接的能力。
  3. 最后,这是我现在应该做的事情和问题

    由于上面提到的问题是不可接受的,我现在应该使用Java的URLConnection / HttpURLConnection类,同时仍然坚持使用Grails。

    连接到服务器并发送简单请求完全没问题,一切正常。当我尝试使用流(客户端中的连接的OutputStream和服务器中的请求的InputStream)时,问题就开始了。打开客户端的OutputStream并将数据写入其中就像它一样容易。但是从请求的InputStream中读取对我来说似乎是不可能的,因为该流似乎是总是空的

    示例代码

    以下是服务器端(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方法不同的数据有效负载,并且一遍又一遍地重新安排代码,但似乎无法解决问题。< / p>

    我希望找到什么

    我希望找到解决问题的方法,当然。任何高度赞赏:提示,代码片段,图书馆建议等。也许我甚至都把它弄错了,需要朝着完全不同的方向前进。

    那么,如何在不手动打开服务器端端口的情况下,为Java客户端向Grails Web应用程序实现相当大(二进制)文件的可恢复文件上传?

2 个答案:

答案 0 :(得分:0)

HTTP GET方法具有用于范围检索的特殊标头:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35大多数下载程序使用它来从服务器进行可恢复的下载。

据我所知,将此标题用于POST / PUT请求没有标准做法,但这取决于你,对吧?你可以制作非常标准的Grails控制器,它接受标准的http上传,标题为Range: bytes=500-999。并且控制器应该将这500个上传的字节从客户端放入文件,从位置500开始

在这种情况下,您不需要打开任何套接字,并制作自己的协议等。

P.S。 500字节只是一个例子,可能你正在使用更大的部分。

答案 1 :(得分: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的文件大小,但对于小尺寸文件,某些字节的代码丢失或甚至没有,但在请求标头内容长度即将到来,不知道为什么它正在发生。)