How to avoid OutOfMemoryError when uploading a large file using Jersey client

前端 未结 6 1998
难免孤独
难免孤独 2020-11-28 08:14

I am using Jersey client for http-based request. It works well if the file is small but run into error when I post a file with size of 700M:

Exception in thr         


        
相关标签:
6条回答
  • 2020-11-28 08:39

    Below is the code for uploading a (potentially large) file with chunked transfer encoding (i.e. streams) using Jersey 2.11.

    Maven:

    <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());
    
    0 讨论(0)
  • 2020-11-28 08:41
    @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";
            }
    
        }
    
    0 讨论(0)
  • 2020-11-28 08:49

    In order for your code not to depend on the size of the uploaded file, you need:

    1. Use streams
    2. Define the chuck size of the jersey client. For example: client.setChunkedEncodingSize(1024);

    Server:

        @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:

        ...
        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);
    
    0 讨论(0)
  • 2020-11-28 08:55

    You could use streams.Try something like this on the client:

    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);      
    

    with resource like this on the server:

    @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
    
    0 讨论(0)
  • 2020-11-28 08:59

    In my case (Jersey 2.23.2) rschmidt13's solution gave this warning:

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

    This can be solved adding the following line:

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

    However I think a cleaner solution can be obtained using the StreamingOutput interface. I post a complete example hoping it could be useful.

    Client (File upload)

    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));
    

    Server

    @Path("resourcename")
    public class MyResource {
    
        @Context
        HttpServletRequest request;
    
        @POST
        @Path("thepath")
        public Response upload() throws IOException, ServletException {
    
            try (InputStream is = request.getInputStream()) {
                // ...
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 09:04

    If possible, can you split the file you send into smaller parts? This will reduce memory usage, but you need to change the code on both sides of the uploading/downloading code.

    If you can't, then your heap space is too low, try increasing it with this JVM parameter. In your application server add/change the Xmx JVM options. For example

    -Xmx1024m
    

    to set Max Heap Space to 1Gb

    0 讨论(0)
提交回复
热议问题