web-dev-qa-db-fra.com

Comment éviter OutOfMemoryError lors du téléchargement d'un fichier volumineux à l'aide du client Jersey

J'utilise le client Jersey pour les requêtes basées sur http. Cela fonctionne bien si le fichier est petit mais en erreur lorsque je poste un fichier de 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)

voici mon code:

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);
16
Mr rain

Vous pouvez utiliser streams.Essayez quelque chose comme ceci sur le 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);      

avec une ressource comme celle-ci sur le serveur:

@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
31
Martin Wilson

Pour que votre code ne dépende pas de la taille du fichier téléchargé, vous avez besoin des éléments suivants:

  1. Utiliser des flux
  2. Définissez la taille du mandrin du client du maillot. Par exemple: client.setChunkedEncodingSize(1024);

Serveur :

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

Vous trouverez ci-dessous le code permettant de télécharger un fichier (potentiellement volumineux) avec un codage de transfert fragmenté (c'est-à-dire des flux) à l'aide de 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());
5
rschmidt13

Dans mon cas (Jersey 2.23.2) la solution de rschmidt13 a donné cet avertissement:

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

Cela peut être résolu en ajoutant la ligne suivante:

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

Cependant, je pense qu'une solution plus propre peut être obtenue en utilisant l'interface StreamingOutput . Je poste un exemple complet dans l'espoir que cela puisse être utile.

Client (téléchargement de fichier)

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

Serveur

@Path("resourcename")
public class MyResource {

    @Context
    HttpServletRequest request;

    @POST
    @Path("thepath")
    public Response upload() throws IOException, ServletException {

        try (InputStream is = request.getInputStream()) {
            // ...
        }
    }
}
1
xonya

Si possible, pouvez-vous diviser le fichier que vous envoyez en parties plus petites? Cela réduira l'utilisation de la mémoire, mais vous devez changer le code des deux côtés du code de téléchargement.

Si vous ne le pouvez pas, votre espace de mémoire est trop faible, essayez de l'augmenter avec ce paramètre de la machine virtuelle Java. Dans votre serveur d'applications, ajoutez/modifiez les options de la JVM Xmx. Par exemple

-Xmx1024m

définir l'espace de tas maximum à 1Go

0
JScoobyCed
@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