使用 JAX-RS 上传文件

Posted

技术标签:

【中文标题】使用 JAX-RS 上传文件【英文标题】:FileUpload with JAX-RS 【发布时间】:2014-11-06 00:07:03 【问题描述】:

我尝试将文件从 javascript 客户端上传到 JAX-RS Java 服务器。

我在我的服务器上使用以下 REST 上传功能:

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

在客户端,我按如下方式发送文件:

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

当我在我的 Grails 控制器中使用代码时,它运行良好,但是当我在 REST 资源中使用它时,我总是得到:请求不是 MultipartHttpServletRequest 或 SecurityContextHolderAwareRequestWrapper

日志输出为

request: com.sun.proxy.$Proxy58

我使用 XMLHttpRequest 从 JavaScript 发送文件 blob,其中包含正文中的 blob 和一些查询参数。

如何使 JAX-RS 文件上传正常工作?如何通过我的 POST 请求接收一些额外的查询参数?

【问题讨论】:

通过这个链接。希望你能澄清你的问题。 mkyong.com/webservices/jax-rs/file-upload-example-in-jersey 【参考方案1】:

在服务器端你可以使用这样的东西

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


同样可以用java中的客户端代码检查

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


使用 html,您可以简单地检查此代码

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

要获取 QueryParam,请检查 @QueryParam 或标题参数使用 @HeaderParam

Example of @QueryParam

Example of @HeaderParam

试试这个,希望对你的问题有帮助。

【讨论】:

如何使用 apache-wink 做到这一点? 值得一提的是,这种依赖FormDataContentDisposition@FormDataParam的方法签名不能在客户端生成动态代理(WebResourceFactory.newResource(...))。所以在客户端,如果你想从服务器接口动态生成一个动态代理,你宁愿使用@PathParam来指定文件名,例如:@POST @Path("/test/fileName") @Consumes(APPLICATION_OCTET_STREAM) void upload(@PathParam("fileName") String fileName, InputStream in) 这不是 JAX-RS 的一部分。它不适用于每个服务器。例如,这适用于使用 Jersey 实现的 Tomcat。 使用这个,pdf文件被完美上传,当上传jpg文件时,它被上传但文件已损坏。有什么建议吗?【参考方案2】:

没有 Jax-RS 方法可以做到这一点。每个服务器都有自己的扩展,都使用多部分表单提交。例如,在 CXF 中,以下内容将允许您通过多部分表单上传。 (附件是 CXF 特定的扩展)

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

而泽西岛的以下内容相同(FormDataParam 是泽西岛的扩展):

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

(我忽略了@Path、@POST 和@Produces,以及其他不相关的注释。)

【讨论】:

我没明白。您能否发布一个完整的示例,说明我如何获取文件字节? 在泽西岛,你得到一个输入流。从那个到 byte[] 应该相对简单。 仍然,我不知道答案。我如何获得字节[]? ***.com/questions/1264709/… 对此有很多答案。 Apache commons-io、Google guava 或纯 Java。 在 cxf 上,这可能是@confile question DataHandler handler= attr.getDataHandler(); InputStream instream = handler.getInputStream(); 的答案【参考方案3】:

在您的表单提交者代码中添加enctype="multipart/form-data" 并在您的@POST 方法中添加@Consumes(MediaType.MULTIPART_FORM_DATA_TYPE),以便我们知道我们正在提交一个多部分文件并且其余的api可以使用它。 你的 rest api 方法可能看起来像

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

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

这将在服务器上创建一个临时文件。它从网络读取并保存到临时文件中。

为了进行防御性编程,我会检查正在上传的文件的内容类型元数据

【讨论】:

【参考方案4】:

这是我们上传文件(在我们的例子中是图片)所做的:服务器端

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


对于客户端,我们使用由另一个团队编写的 AngularJS。我将无法解释它,但这里是代码:

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

还有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;
;

希望对你有帮助...

【讨论】:

什么是 MultipartFormDataInput。是哪个进口的? 它由库 org.jboss.resteasy:resteasy-multipart-provider:3.0.8.Final 提供。 但这不是泽西岛? 没有。也许我在写答案时错过了“泽西”标签:) 你有泽西岛的答案吗?【参考方案5】:

使用纯 JAX-RS,假设您不需要文件名,上传方法如下:

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public void upload(InputStream file, @QueryParam("foo") String foo) 
        // Read file contents from the InputStream and do whatever you need
    

【讨论】:

【参考方案6】:

这仅适用于文件。

  @POST
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  public Response upload(Map<String, InputStream> files) 
      return Response.ok().build();
  

但我仍在寻找将 json 添加到请求中。

也许,JAX-RS Spec 的 4.2.1 章节是在实践中实现最纯粹的方法的方式。那将是实现一个提供者:一个 MessageBodyReader 专业化。

【讨论】:

我想我要放弃了。 entityStream 太难处理了。我没有多少时间来处理它。需要选择以下两个选项之一:用于处理多部分/表单数据有效负载的第三方库或基于 JAX-RS 的容器实现来实现资源类。在第二个选项中,最好将其与其他资源实现分开并且非常不同,这样更改任何基于容器的实现会更容易。

以上是关于使用 JAX-RS 上传文件的主要内容,如果未能解决你的问题,请参考以下文章

IBM Mobilefirst Java HTTP Adapter 中的多部分文件上传

如何在 InputStream 中接收多个文件并进行相应处理?

nodejs上传的文件如何使用

如何在使用 jQuery 上传文件之前从文件上传控件中删除文件

使用CURL上传文件

thinkphp文件上传多文件上传和单文件上传不能一起使用吗