【问题标题】:Resumable upload from Java client to Grails web application?从 Java 客户端到 Grails Web 应用程序的可恢复上传?
【发布时间】:2012-07-06 15:12:54
【问题描述】:

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

首先,这是我想做的事情

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

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

到目前为止,这是我所做的

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

问题在于

其实这样至少有两个问题:

  1. 我自己打开这些端口,所以我必须自己管理打开和使用的端口。这很容易出错。
  2. 实际上,我绕过了 Grails 管理大量(并发)连接的能力。

最后,我现在应该做什么和问题

由于我上面提到的问题是不可接受的,我现在应该使用 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 方法不同的数据负载,还一遍遍地重新排列代码,但似乎无法解决问题。

我希望找到什么

当然,我希望找到解决问题的方法。任何东西都会受到高度赞赏:提示、代码 sn-ps、库建议等等。也许我什至都错了,需要朝着完全不同的方向前进。

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

【问题讨论】:

    标签: java grails upload inputstream resume-upload


    【解决方案1】:

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

    据我了解,对于 POST/PUT 请求使用此标头没有标准做法,但这取决于您,对吗?你可以制作非常标准的 Grails 控制器,它将接受标准的 http 上传,带有像 Range: bytes=500-999 这样的标头。控制器应该将这 500 个从客户端上传的字节放入文件中,从位置 500 开始

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

    附: 500 字节只是一个示例,您可能使用了更大的部分。

    【讨论】:

      【解决方案2】:

      客户端 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 的文件大小,但对于小文件,一些字节的代码丢失或什至没有出现,但在请求标头内容长度即将到来,不知道为什么它正在发生。)

      【讨论】:

        猜你喜欢
        • 2018-10-09
        • 1970-01-01
        • 2015-03-07
        • 2019-03-02
        • 1970-01-01
        • 1970-01-01
        • 2011-05-30
        • 2013-01-12
        • 1970-01-01
        相关资源
        最近更新 更多