web-dev-qa-db-fra.com

FileUpload avec JAX-RS

J'essaie de télécharger des fichiers depuis un client JavaScript vers un serveur JAX-RS Java server.

J'utilise la fonction de téléchargement REST suivante sur mon serveur:

@POST
@Produces('application/json')
UploadDto upload(
        @Context HttpServletRequest request,
        @QueryParam("cookie") String cookie) {

    def contentType
    byte [] fileBytes

    log.debug "upload - cookie: "+cookie

    try{
        if (request instanceof MultipartHttpServletRequest) {
            log.debug "request instanceof MultipartHttpServletRequest"

            MultipartHttpServletRequest myrequest = request
            CommonsMultipartFile file = (CommonsMultipartFile) myrequest.getFile('file')
            fileBytes = file.bytes
            contentType = file.contentType
            log.debug ">>>>> upload size of the file in byte: "+ file.size
        }
        else if (request instanceof SecurityContextHolderAwareRequestWrapper) {
            log.debug "request instanceof SecurityContextHolderAwareRequestWrapper"

            SecurityContextHolderAwareRequestWrapper myrequest = request

            //get uploaded file's inputStream
            InputStream inputStream = myrequest.inputStream

            fileBytes = IOUtils.toByteArray(inputStream);
            contentType = myrequest.getHeader("Content-Type")
            log.debug ">>>>> upload size of the file in byte: "+ fileBytes.size()
        }
        else {
            log.error "request is not a MultipartHttpServletRequest or SecurityContextHolderAwareRequestWrapper"
            println "request: "+request.class
        }
    }
    catch (IOException e) {
        log.error("upload() failed to save file error: ", e)
    }
}

Côté client, j'envoie le fichier comme suit:

var str2ab_blobreader = function(str, callback) {
    var blob;
    BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder
            || window.BlobBuilder;
    if (typeof (BlobBuilder) !== 'undefined') {
        var bb = new BlobBuilder();
        bb.append(str);
        blob = bb.getBlob();
    } else {
        blob = new Blob([ str ]);
    }
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result)
    }
    f.readAsArrayBuffer(blob);
}

var fileName = "fileName.jpg";
var contentType = "image/jpeg";
if (file.type.toString().toLowerCase().indexOf("png") > -1) {
    fileName = "fileName.png";
    contentType = "image/png";
}

var xhrNativeObject = new XMLHttpRequest();
var urlParams = ?test=123;
xhrNativeObject.open("post", url + urlParams, true);
xhrNativeObject.setRequestHeader("Content-Type", contentType);

xhrNativeObject.onload = function(event) {

    var targetResponse = event.currentTarget;
    if ((targetResponse.readyState == 4)
            && (targetResponse.status == 200)) {
        var obj = JSON.parse(targetResponse.responseText);
        console.log(obj.uploadImageId);
    } else {
        console.log("fail");
    }
}

var buffer = str2ab_blobreader(file, function(buf) {
    xhrNativeObject.send(buf);
});

Lorsque j'utilise le code dans mon Grails Controller, cela a bien fonctionné mais quand je l'utilise dans une ressource REST que j'obtiens toujours: la demande n'est pas un MultipartHttpServletRequest ou SecurityContextHolderAwareRequestWrapper

La sortie du journal est

request: com.Sun.proxy.$Proxy58

L'envoi d'un blob de fichier à partir de JavaScript J'utilise XMLHttpRequest qui contient le blob dans le corps et certains paramètres de requête.

Comment puis-je faire fonctionner le téléchargement de fichiers JAX-RS? Comment puis-je recevoir des paramètres de requête supplémentaires avec ma POST?

22
confile

Côté serveur, vous pouvez utiliser quelque chose comme ça

@POST
@Path("/fileupload")  //Your Path or URL to call this service
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
        @DefaultValue("true") @FormDataParam("enabled") boolean enabled,
        @FormDataParam("file") InputStream uploadedInputStream,
        @FormDataParam("file") FormDataContentDisposition fileDetail) {
     //Your local disk path where you want to store the file
    String uploadedFileLocation = "D://uploadedFiles/" + fileDetail.getFileName();
    System.out.println(uploadedFileLocation);
    // save it
    File  objFile=new File(uploadedFileLocation);
    if(objFile.exists())
    {
        objFile.delete();

    }

    saveToFile(uploadedInputStream, uploadedFileLocation);

    String output = "File uploaded via Jersey based RESTFul Webservice to: " + uploadedFileLocation;

    return Response.status(200).entity(output).build();

}
private void saveToFile(InputStream uploadedInputStream,
        String uploadedFileLocation) {

    try {
        OutputStream out = null;
        int read = 0;
        byte[] bytes = new byte[1024];

        out = new FileOutputStream(new File(uploadedFileLocation));
        while ((read = uploadedInputStream.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
        out.close();
    } catch (IOException e) {

        e.printStackTrace();
    }

}

Encore une fois, cela peut être vérifié avec le code client dans Java avec

public class TryFile {
public static void main(String[] ar)
       throws HttpException, IOException, URISyntaxException {
    TryFile t = new TryFile();
    t.method();
}
public void method() throws HttpException, IOException, URISyntaxException {
    String url = "http://localhost:8080/...../fileupload";  //Your service URL
    String fileName = ""; //file name to be uploaded
    HttpClient httpclient = new DefaultHttpClient();
    HttpPost httppost = new HttpPost(url);
    FileBody fileContent = new FiSystem.out.println("hello");
    StringBody comment = new StringBody("Filename: " + fileName);
    MultipartEntity reqEntity = new MultipartEntity();
    reqEntity.addPart("file", fileContent);
    httppost.setEntity(reqEntity);

    HttpResponse response = httpclient.execute(httppost);
    HttpEntity resEntity = response.getEntity();
}
}

Avec HTML, vous pouvez simplement vérifier avec ce code

<html>
<body>
<h1>Upload File with RESTFul WebService</h1>
<form action="<Your service URL (htp://localhost:8080/.../fileupload)" method="post" enctype="multipart/form-data">
   <p>
    Choose a file : <input type="file" name="file" />
   </p>
   <input type="submit" value="Upload" />
</form>

Pour obtenir QueryParam, vérifiez @QueryParam ou pour les paramètres d'en-tête, utilisez @HeaderParam

Exemple de @QueryParam

Exemple de @HeaderParam

Essayez ceci, j'espère que cela vous aidera à résoudre votre problème.

39
ajitksharma

Il n'y a aucun moyen Jax-RS de le faire. Chaque serveur a ses propres extensions, toutes utilisant des soumissions de formulaires en plusieurs parties. Par exemple, dans CXF, ce qui suit vous permettra de télécharger via un formulaire en plusieurs parties. (La pièce jointe est une extension spécifique à CXF)

@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@Multipart(value = "vendor") String vendor,
        @Multipart(value = "uploadedFile") Attachment attr) {

alors que ce qui suit est le même pour Jersey (FormDataParam est une extension Jersey):

 @Consumes(MediaType.MULTIPART_FORM_DATA_TYPE)
 public String postForm(
         @DefaultValue("true") @FormDataParam("enabled") boolean enabled,
         @FormDataParam("data") FileData bean,
         @FormDataParam("file") InputStream file,
         @FormDataParam("file") FormDataContentDisposition fileDisposition) {

(J'ai ignoré les @Path, @POST et @Produces et autres annotations non pertinentes.)

12
Jeff Wang

Ajoutez enctype="multipart/form-data" Dans votre code de soumission de formulaire et @Consumes(MediaType.MULTIPART_FORM_DATA_TYPE) sur votre méthode @POST afin que nous sachions que nous soumettons un fichier en plusieurs parties et que les autres api puissent le consommer. Votre méthode api de repos pourrait ressembler à

@POST
@Path("/uploadfile")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response upload(
    @FormDataParam("file") InputStream fileInputStream, 
    @FormDataParam("file") FormDataContentDisposition disposition) {
        //...
}

ou

 @POST
 @Path("/uploadfile")
 public void post(File file) {
    Reader reader = new Reader(new FileInputStream(file));
    // ... 
 }

Cela créera un fichier temporaire sur le serveur. Il lit à partir du réseau et enregistre dans le fichier temporaire.

Pour programmer de manière défensive, je vérifierais les métadonnées de type de contenu du fichier en cours de téléchargement.

8
Atul

Voici ce que nous avons fait pour télécharger le fichier (images dans notre cas):
Côté serveur

@POST
@RolesAllowed("USER")
@Path("/upload")
@Consumes("multipart/form-data")
public Response uploadFile(MultipartFormDataInput input) throws IOException
{
    File local;
    final String UPLOADED_FILE_PATH = filesRoot; // Check applicationContext-Server.properties file

    //Get API input data
    Map<String, List<InputPart>> uploadForm = input.getFormDataMap();

    //The file name
    String fileName;
    String pathFileName = "";


    //Get file data to save
    List<InputPart> inputParts = uploadForm.get("attachment");

    try
    {
        for (InputPart inputPart : inputParts)
        {
            //Use this header for extra processing if required
            MultivaluedMap<String, String> header = inputPart.getHeaders();
            fileName = getFileName(header);
            String tmp = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());
            pathFileName = "images/upload/" + tmp + '_' + fileName + ".png";
            fileName = UPLOADED_FILE_PATH + pathFileName;

            // convert the uploaded file to input stream
            InputStream inputStream = inputPart.getBody(InputStream.class, null);

            byte[] bytes = IOUtils.toByteArray(inputStream);
            // constructs upload file path

            writeFile(bytes, fileName);
            // NOTE : The Target picture boundary is 800x600. Should be specified somewhere else ?
            BufferedImage scaledP = getScaledPicture(fileName, 800, 600, RenderingHints.VALUE_INTERPOLATION_BILINEAR, false);
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(scaledP, "png", os);
            local = new File(fileName);
            ImageIO.write(scaledP, "png", local);
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
        return Response.serverError().build();
    }
    return Response.status(201).entity(pathFileName).build();

}

Pour le côté client, nous utilisons AngularJS qui est codé par une autre équipe. Je ne pourrai pas vous l'expliquer, mais voici le code:

    $scope.setPicture = function (element)
{
  var t = new Date();
  console.log(t + ' - ' + t.getMilliseconds());

  // Only process image files.
  if (!element[0].type.match('image.*'))
  {
    console.log('File is not an image');
    Error.current.element = $document[0].getElementById('comet-project-upload');
    Error.current.message = 'Please select a picture.';
    $scope.$apply();
  }
  else if (element[0].size > 10 * 1024 * 1024)
  {
    console.log('File is too big');
    Error.current.element = $document[0].getElementById('comet-project-upload');
    Error.current.message = 'File is too big. Please select another file.';
    $scope.$apply();
  }
  else
  {
    self.animSpinner = true;

    var fd = new FormData();
    //Take the first file
    fd.append('attachment', element[0]);
    //Note : attachment is the compulsory name ?

    Project.uploadImage(fd).then(
      function (data)
      {
        self.animSpinner = false;

        // self.$apply not needed because $digest already in progress
        self.projectPicture = data;
      },
      function ()
      {
        self.animSpinner = false;
        Error.current.element = $document[0].getElementById('comet-project-upload');
        Error.current.message = 'Error with the server when uploading the image';

        console.error('Picture Upload failed! ' + status + ' ' + headers + ' ' + config);
      }
    );
  }
};

Et la fonction uploadImage:

    this.uploadImage = function (imageData)
{
  var deferred = $q.defer();

  $http.post('/comet/api/image/upload', imageData,
    {
      headers: { 'Content-Type': undefined, Authorization: User.hash },
      //This method will allow us to change how the data is sent up to the server
      // for which we'll need to encapsulate the model data in 'FormData'
      transformRequest: angular.identity
      //The cool part is the undefined content-type and the transformRequest: angular.identity
      // that give at the $http the ability to choose the right "content-type" and manage
      // the boundary needed when handling multipart data.
    })
    .success(function (data/*, status, headers, config*/)
    {
      deferred.resolve(data);
    })
    .error(function (data, status, headers, config)
    {
      console.error('Picture Upload failed! ' + status + ' ' + headers + ' ' + config);
      deferred.reject();
    });

  return deferred.promise;
};

J'espère que cela vous aidera ...

5
Snorky35