For a new project, we are using jQuery components on the client side, one of which is the blueImp file uploader. We were writing code happily, and everything worked great in Chrome and Firefox ... until somebody tried to open the site in Internet Explorer. Apparently, IE can't handle the application/json return from the server when using this upload component - moreover, it just streams it to the user as a file. Anyway, a lot of users do have this problem (which is mentioned on their site: https://github.com/blueimp/jQuery-File-Upload/wiki/Frequently-Asked-Questions and elsewhere on their bug reporter)

However, most of the workarounds mentioned there are based on PHP. We are using Java on the server-side, more specifically: JAX-RS. Now, JAX-RS has this lovely @Produces annotation, which is ... well, quite static. I've been digging through the documentation, but came out none the wiser. Is there any way I can add a condition to this @Produces annotation? To make it clear: I want to return text/plain (or something like that) when the user is using IE, and application/json when the user is using a browser ... eeeerrrm, I mean, some OTHER browser :-)

Thanks!

有帮助吗?

解决方案

I eventuallly solved the issue by writing my own Provider (which is actually what I started to do before I asked the question here). For those interested (and don't know yet): writing your own provider involves 2 steps:

  • adding @Provider to your class, and then @Produces()
  • implementing the MessageBodyWriter interfaces, overriding the necessary methods

My code ended up being:

package com.mypackage;    

import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.mypackage.UploadResponse;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

@Produces("text/plain")
@Provider
public class UploadResponseProvider implements MessageBodyWriter<UploadResponse> {
    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
         /* You could check the type here, or do some additional checks, but I can just return true, because if it is an UploadResponse (which is inferred via the generic), it's all ok */ 
        return true;
    }

    @Override
    public long getSize(UploadResponse uploadResponse, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(UploadResponse uploadResponse, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
        OutputStreamWriter writer = new OutputStreamWriter(entityStream);
        writer.write(new Gson().toJson(Lists.newArrayList(uploadResponse)));
        writer.flush();
    }

}

Just to explain this code a little bit: UploadResponse is my object to return. It's a simple POJO, with the fields url, size and name, with getters and setters.

I read that returning text/plain makes the blueImp jQuery Fileupload functional, so this is a Provider for text/plain output of a UploadResponse.

What I do here is creating a JSON object, put it in a list, and write that list to the response. I'm creating a list of UploadResponses, because my UI expects that. The blueImp File Upload expects that by default, btw. We're doing auto-upload on JAX-RS, and a hard limit to 1 file, so I don't have to handle more than 1 item. Beware of that when reusing this code, it might require some adaptations.

As you can see, that's all I'm doing, no more. The rest is just default implementation, because in my case, I don't care about any of it.

A small note: DON'T close the writer. Just flush it. Closing it will close it before writing to the response.

其他提示

Using a Servlet Filter should work. Here is an (untested) one:

package blah;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class IEFixFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, 
                         ServletResponse resp, 
                         FilterChain chain) throws IOException, ServletException
    {
        chain.doFilter(req, resp);

        String userAgent = ((HttpServletRequest) req).getHeader("User-Agent");
        if(userAgent.contains("MSIE")) {
            response.setContentType("text/plain");
        }
    }

    @Override
    public void init(FilterConfig cfg) throws ServletException {}

    @Override
    public void destroy() {}
}

And the config you add into web.xml. You need to change blah to the package in which you put the above class and url-pattern should match the url you use for the file upload.

  <filter>
    <filter-name>ieFixFilter</filter-name>
    <filter-class>blah.IEFixFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>ieFixFilter</filter-name>
    <url-pattern>/upload/url/*</url-pattern>
  </filter-mapping>

This might be of no use at this late stage but I had this exact requirement recently and it's quite an easy fix. Let's assume you are returning an octet-stream but if something goes wrong you'd like to return just plain JSON.

  1. Don't specify the @Produces in the declaration of your restful method.
  2. Based on what happens in your method you are going to be doing this kind of thing

    return Response.status(200).entity(file).type(MediaType.APPLICATION_OCTET_STREAM).build();
    

So, if for instance you needed to return early with a JSON object instead of an octet-stream, then just do

    return Response.status(500).entity(errorObject).type(MediaType.APPLICATION_JSON).build();

The important thing is that you indicate the MediaType as you build the response.

Hope this helps someone.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top