【问题标题】:How to avoid OutOfMemoryError when uploading a large file using Jersey client使用 Jersey 客户端上传大文件时如何避免 OutOfMemoryError
【发布时间】:2012-04-26 02:32:24
【问题描述】:

我正在使用 Jersey 客户端进行基于 http 的请求。如果文件很小但在我发布大小为 700M 的文件时出错:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2786)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:94)
    at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:61)
    at com.sun.jersey.api.client.CommittingOutputStream.write(CommittingOutputStream.java:90)
    at com.sun.jersey.core.util.ReaderWriter.writeTo(ReaderWriter.java:115)
    at com.sun.jersey.core.provider.AbstractMessageReaderWriterProvider.writeTo(AbstractMessageReaderWriterProvider.java:76)
    at com.sun.jersey.core.impl.provider.entity.FileProvider.writeTo(FileProvider.java:103)
    at com.sun.jersey.core.impl.provider.entity.FileProvider.writeTo(FileProvider.java:64)
    at com.sun.jersey.multipart.impl.MultiPartWriter.writeTo(MultiPartWriter.java:224)
    at com.sun.jersey.multipart.impl.MultiPartWriter.writeTo(MultiPartWriter.java:71)
    at com.sun.jersey.api.client.RequestWriter.writeRequestEntity(RequestWriter.java:300)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler._invoke(URLConnectionClientHandler.java:204)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:147)
    at com.sun.jersey.api.client.Client.handle(Client.java:648)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:680)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:568)
    at TestHttpRequest.main(TestHttpRequest.java:42)

这是我的代码:

ClientConfig cc = new DefaultClientConfig();
        Client client = Client.create(cc);
        WebResource resource = client.resource("http://localhost:8080/JerseyWithServletTest/helloworld");
        FormDataMultiPart form = new FormDataMultiPart();
        File file = new File("E:/CN_WXPPSP3_v312.ISO");
        form.field("username", "ljy");
        form.field("password", "password");
        form.field("filename", file.getName());
        form.bodyPart(new FileDataBodyPart("file", file, MediaType.MULTIPART_FORM_DATA_TYPE));
        ClientResponse response = resource.type(MediaType.MULTIPART_FORM_DATA).post(ClientResponse.class, form);

【问题讨论】:

标签: file-upload jersey large-files


【解决方案1】:

您可以使用流。在客户端上尝试这样的操作:

InputStream fileInStream = new FileInputStream(fileName);
String sContentDisposition = "attachment; filename=\"" + fileName.getName()+"\"";
WebResource fileResource = a_client.resource(a_sUrl);       
ClientResponse response = fileResource.type(MediaType.APPLICATION_OCTET_STREAM)
                        .header("Content-Disposition", sContentDisposition)
                        .post(ClientResponse.class, fileInStream);      

在服务器上有这样的资源:

@PUT
@Consumes("application/octet-stream")
public Response putFile(@Context HttpServletRequest a_request,
                         @PathParam("fileId") long a_fileId,
                         InputStream a_fileInputStream) throws Throwable
{
    // Do something with a_fileInputStream
    // etc

【讨论】:

  • 但是,我想发布一些参数,例如文件名。
  • 我的示例展示了如何在标题中包含文件名。您可以从 URL 获取文件资源的标识符 - 在我的示例中显示为 fileId。
  • 为什么这不在球衣的例子中??如此明显的用例!
  • 如果我想发布带有多个文本字段和大文件的表单怎么办?使用普通的 Servlet 和 FilePart 是可行的。但我在球衣上遇到 HeapSpace 错误。
【解决方案2】:

为了让您的代码不依赖于上传文件的大小,您需要:

  1. 使用流
  2. 定义球衣客户端的夹头尺寸。例如: client.setChunkedEncodingSize(1024);

服务器

    @POST
    @Path("/upload/{attachmentName}")
    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
    public void uploadAttachment(@PathParam("attachmentName") String attachmentName, InputStream attachmentInputStream) {
        // do something with the input stream
    }

客户

    ...
    client.setChunkedEncodingSize(1024);
    WebResource rootResource = client.resource("your-server-base-url");
    File file = new File("your-file-path");
    InputStream fileInStream = new FileInputStream(file);
    String contentDisposition = "attachment; filename=\"" + file.getName() + "\"";
    ClientResponse response = rootResource.path("attachment").path("upload").path("your-file-name")
            .type(MediaType.APPLICATION_OCTET_STREAM).header("Content-Disposition", contentDisposition)
            .post(ClientResponse.class, fileInStream);

【讨论】:

    【解决方案3】:

    以下是使用 Jersey 2.11 上传具有分块传输编码(即流)的(可能很大)文件的代码。

    马文:

    <properties>
        <jersey.version>2.11</jersey.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-multipart</artifactId>
            <version>${jersey.version}</version>
        </dependency>
    <dependencies>
    

    Java:

    Client client = ClientBuilder.newClient(clientConfig);
    client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED");
    
    WebTarget target = client.target(SERVICE_URI); 
    InputStream fileInStream = new FileInputStream(inFile);
    String contentDisposition = "attachment; filename=\"" + inFile.getName() + "\"";
    System.out.println("sending: " + inFile.length() + " bytes...");
    Response response = target
                .request(MediaType.APPLICATION_OCTET_STREAM_TYPE)
                .header("Content-Disposition", contentDisposition)
                .header("Content-Length", (int) inFile.length())
                .put(Entity.entity(fileInStream, MediaType.APPLICATION_OCTET_STREAM_TYPE));
    System.out.println("Response status: " + response.getStatus());
    

    【讨论】:

    • 在 Jersey 1 中只是设置(当然使用适当的 jersey 1 API)client.property(ClientProperties.CHUNKED_ENCODING_SIZE, chunkSize1mb);似乎成功了。然而,它似乎不再适用于 Jersey 2。我还设置了这个答案中建议的属性 (client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED"); ) 并且它有效!所以非常感谢!你知道这是否会破坏其他东西吗?
    • 在我的情况下,这个解决方案只能添加System.setProperty("sun.net.http.allowRestrictedHeaders", "true")才能正常工作。我使用StreamingOutput 添加了一个替代解决方案的答案。无论如何,设置 CHUNKED 客户端属性可以让一切正常运行,谢谢!
    【解决方案4】:

    在我的情况下(泽西 2.23.2)rschmidt13's solution 给出了这个警告:

    WARNING: Attempt to send restricted header(s) while the [sun.net.http.allowRestrictedHeaders] system property not set. Header(s) will possibly be ignored.
    

    这可以通过添加以下行来解决:

    System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
    

    但是我认为使用StreamingOutput 接口可以获得更清洁的解决方案。 我发布了一个完整的示例,希望它可能有用。

    客户端(文件上传)

    WebTarget target = ClientBuilder.newBuilder().build()
                .property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024)
                .property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED")
                .target("<your-url>");
    
    StreamingOutput out = new StreamingOutput() {
    
        @Override
        public void write(OutputStream output) throws IOException, 
                WebApplicationException {
    
            try (FileInputStream is = new FileInputStream(file)) {
    
                int available;
                while ((available = is.available()) > 0) {
                    // or use a buffer
                    output.write(is.read());
                }
            }
        }
    };
    
    Response response = target.request().post(Entity.text(out));
    

    服务器

    @Path("resourcename")
    public class MyResource {
    
        @Context
        HttpServletRequest request;
    
        @POST
        @Path("thepath")
        public Response upload() throws IOException, ServletException {
    
            try (InputStream is = request.getInputStream()) {
                // ...
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      如果可能,您能否将发送的文件拆分成更小的部分?这样会减少内存占用,但是需要在上传/下载代码两边更改代码。

      如果你不能,那么你的堆空间太小了,试着用这个 JVM 参数增加它。在您的应用程序服务器中添加/更改Xmx JVM 选项。例如

      -Xmx1024m
      

      将最大堆空间设置为 1Gb

      【讨论】:

      • 感谢您的回答,我的项目是为外部系统提供服务。
      • 我明白了,我想知道我们是否可以在文件上传期间刷新内存。以及怎么做!!
      • 通过将文件拆分成更小的部分,您将获得更好的控制,并且每个文件的内存使用情况可预测。这需要在您的服务器端和客户端进行更改
      • 是的,这是个好主意,谢谢你的建议,JScoobyCed。
      • 我已将文件拆分为 10M 大小的较小部分,并在请求中上传所有部分。但是还是不行,我要疯了!!!
      【解决方案6】:
      @Consumes("multipart/form-data")
      @Produces(MediaType.TEXT_PLAIN + ";charset=utf-8")
      public String upload(MultipartFormDataInput input,  @QueryParam("videoId") String  videoId,
              @Context HttpServletRequest a_request) {
      
          String fileName = "";
          for (InputPart inputPart : input.getParts()) {
              try {
      
                  MultivaluedMap<String, String> header = inputPart.getHeaders();
                  fileName = getFileName(header);
                  // convert the uploaded file to inputstream
                  InputStream inputStream = inputPart.getBody(InputStream.class, null);               
                  // write the inputStream to a FileOutputStream
                  OutputStream outputStream = new FileOutputStream(new File("/home/mh/Téléchargements/videoUpload.avi"));
                  int read = 0;
                  byte[] bytes = new byte[1024];
                  while ((read = inputStream.read(bytes)) != -1) {
                      outputStream.write(bytes, 0, read);
                  }
                  System.out.println("Done!");
              } catch (IOException e) {
                  e.printStackTrace();
                  return "ko";
              }
      
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-07-11
        • 1970-01-01
        • 2014-09-03
        • 2011-07-14
        • 2017-03-20
        • 1970-01-01
        • 2010-12-14
        • 2016-04-23
        相关资源
        最近更新 更多