Caricamento file multipart su Google Appengine utilizzando jersey-1.7

Ho scritto un’applicazione su Google Appengine con Jersey per gestire il caricamento di file semplice. Funziona bene quando era sulla maglia 1.2. Nelle versioni successive (corrente 1.7) @FormDataParam viene introdotto per gestire gli input multipart / form. Sto usando jersey-multipart e la dipendenza di mimepull. Sembra che il nuovo modo di farlo sia creare file temporanei in appengine che tutti sappiamo essere illegali …

Mi sto perdendo qualcosa o sto facendo qualcosa di sbagliato qui dal momento che Jersey è ora apparentemente compatibile con AppEngine?

@POST @Path("upload") @Consumes(MediaType.MULTIPART_FORM_DATA) public void upload(@FormDataParam("file") InputStream in) { .... } 

Quanto sopra non funzionerà se chiamato con queste eccezioni …

 /upload java.lang.SecurityException: Unable to create temporary file at java.io.File.checkAndCreate(File.java:1778) at java.io.File.createTempFile(File.java:1870) at java.io.File.createTempFile(File.java:1907) at org.jvnet.mimepull.MemoryData.createNext(MemoryData.java:87) at org.jvnet.mimepull.Chunk.createNext(Chunk.java:59) at org.jvnet.mimepull.DataHead.addBody(DataHead.java:82) at org.jvnet.mimepull.MIMEPart.addBody(MIMEPart.java:192) at org.jvnet.mimepull.MIMEMessage.makeProgress(MIMEMessage.java:235) at org.jvnet.mimepull.MIMEMessage.parseAll(MIMEMessage.java:176) at org.jvnet.mimepull.MIMEMessage.getAttachments(MIMEMessage.java:101) at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:177) at com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80) at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:139) at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:77) at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:474) at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:538) 

Qualcuno ha un indizio? C’è un modo per fare cosa impedendo al mimepull di creare il file temporaneo?

Per i file oltre la sua dimensione predefinita, multipart creerà un file temporaneo. Per evitare ciò – la creazione di un file è imansible su gae – è ansible creare un file jersey-multipart-config.properties nella cartella delle risorse del progetto e aggiungervi questa riga:

 bufferThreshold = -1 

Quindi, il codice è quello che hai dato:

 @POST @Consumes(MediaType.MULTIPART_FORM_DATA) public Response post(@FormDataParam("file") InputStream stream, @FormDataParam("file") FormDataContentDisposition disposition) throws IOException { post(file, disposition.getFileName()); return Response.ok().build(); } 

Per il beneficio di chi ha difficoltà ad utilizzare Eclipse con GPE (Google Plugin per Eclipse), fornisco questa soluzione leggermente modificata derivata dalla risposta di @yves.

L’ho provato con App Engine SDK 1.9.10 e Jersey 2.12 . Non funzionerà con App Engine SDK 1.9.6 -> 1.9.9 tra gli altri a causa di un altro problema.

Sotto la tua cartella \war\WEB-INF\classs crea un nuovo file chiamato jersey-multipart-config.properties . Modifica il file in modo che contenga la riga jersey.config.multipart.bufferThreshold = -1 .

Nota che la cartella \classs è nascosta in Eclipse, quindi cerca la cartella nel file explorer del tuo sistema operativo (ad es. Windows Explorer).

Ora, sia quando la funzione multipart viene inizializzata (sull’inizializzazione del servlet di Jersey) e quando viene effettuato un caricamento di file (su richiesta post servlet di Jersey) il file temporaneo non verrà più creato e GAE non si lamenterà.

È molto importante inserire il file jersey-multipart-config.properties in WEB-INF/classs all’interno di WAR.

Di solito in una struttura di file WAR si inseriscono i file di configurazione ( web.xml , appengine-web.xml ) in WEB-INF/ , ma qui è necessario inserirlo in WEB-INF/classs .

Esempio di configurazione di Maven:

   org.apache.maven.plugins maven-war-plugin 2.4  true   ${basedir}/src/main/webapp/WEB-INF true WEB-INF   ${basedir}/src/main/resources WEB-INF/classs     

E la struttura del tuo progetto può essere simile a:

Struttura del progetto

Contenuto di jersey-multipart-config.properties con Jersey 2.x:

 jersey.config.multipart.bufferThreshold = -1 

Ho trovato la soluzione per evitare di utilizzare la creazione di file temporanea (molto utile per l’implementazione di GAE)

La mia soluzione consiste nella creazione di un nuovo provider MultiPartReader … sotto il mio codice


  @Provider @Consumes("multipart/*") public class GaeMultiPartReader implements MessageBodyReader { final Log logger = org.apache.commons.logging.LogFactory.getLog(getClass()); private final Providers providers; private final CloseableService closeableService; private final MIMEConfig mimeConfig; private String getFixedHeaderValue(Header h) { String result = h.getValue(); if (h.getName().equals("Content-Disposition") && (result.indexOf("filename=") != -1)) { try { result = new String(result.getBytes(), "utf8"); } catch (UnsupportedEncodingException e) { final String msg = "Can't convert header \"Content-Disposition\" to UTF8 format."; logger.error(msg,e); throw new RuntimeException(msg); } } return result; } public GaeMultiPartReader(@Context Providers providers, @Context MultiPartConfig config, @Context CloseableService closeableService) { this.providers = providers; if (config == null) { final String msg = "The MultiPartConfig instance we expected is not present. " + "Have you registered the MultiPartConfigProvider class?"; logger.error( msg ); throw new IllegalArgumentException(msg); } this.closeableService = closeableService; mimeConfig = new MIMEConfig(); //mimeConfig.setMemoryThreshold(config.getBufferThreshold()); mimeConfig.setMemoryThreshold(-1L); // GAE FIX } @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return MultiPart.class.isAssignableFrom(type); } @Override public MultiPart readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap headers, InputStream stream) throws IOException, WebApplicationException { try { MIMEMessage mm = new MIMEMessage(stream, mediaType.getParameters().get("boundary"), mimeConfig); boolean formData = false; MultiPart multiPart = null; if (MediaTypes.typeEquals(mediaType, MediaType.MULTIPART_FORM_DATA_TYPE)) { multiPart = new FormDataMultiPart(); formData = true; } else { multiPart = new MultiPart(); } multiPart.setProviders(providers); if (!formData) { multiPart.setMediaType(mediaType); } for (MIMEPart mp : mm.getAttachments()) { BodyPart bodyPart = null; if (formData) { bodyPart = new FormDataBodyPart(); } else { bodyPart = new BodyPart(); } bodyPart.setProviders(providers); for (Header h : mp.getAllHeaders()) { bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h)); } try { String contentType = bodyPart.getHeaders().getFirst("Content-Type"); if (contentType != null) { bodyPart.setMediaType(MediaType.valueOf(contentType)); } bodyPart.getContentDisposition(); } catch (IllegalArgumentException ex) { logger.error( "readFrom error", ex ); throw new WebApplicationException(ex, 400); } bodyPart.setEntity(new BodyPartEntity(mp)); multiPart.getBodyParts().add(bodyPart); } if (closeableService != null) { closeableService.add(multiPart); } return multiPart; } catch (MIMEParsingException ex) { logger.error( "readFrom error", ex ); throw new WebApplicationException(ex, 400); } } } 

Abbiamo riscontrato un problema simile, Jetty non ci ha permesso di caricare file più di 9194 byte, (all’improvviso – un giorno), ci siamo resi conto in seguito che qualcuno aveva preso l’accesso utente da / tmp, che corrisponde a java.io. tmpdir su alcune versioni di Linux, quindi Jetty non ha potuto memorizzare lì il file caricato e abbiamo ricevuto un errore 400.