ExtJS 4 Spring 3 file upload. Server sends bad response content type
I was creating file upload using ExtJS 4 frontend and Spring 3 as backend. File upload works, but the response from server has wrong content type. When I send {success:true}
using Map<String, Object>
serialized by Jackson, ExtJS returns error
Uncaught Ext.Error: You're trying to decode an invalid JSON String: <pre style="word-wrap: break-word; white-space: pre-wrap;">{"success":true}</pre>
Why is my response wrapped with <pre>
tag? I've searched and found out that I should change response type to text/html
for example. But changing content type in servlet response didn't help
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody Map<String, Object> upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
response.setContentType("text/html");
// File processing
Map<String, Object> jsonResult = new HashMap<String, Object>();
jsonResult.put("success", Boolean.TRUE);
return jsonResult;
}
When I change return value of upload
method to String
, everythin开发者_StackOverflow社区g works correctly, but I want to return Map
and have it serialized by Jackson
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody String upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
// File processing
return "{success:true}";
}
My Spring configuration
<bean
id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
</bean>
<bean
id="jacksonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
<ref bean="stringHttpMessageConverter" />
</list>
</property>
</bean>
How to tell Spring to return correct content type? Why is response of this method incorrect when response of other methods is interpreted correctly?
You need to set the content-type of response as "text/html". If the content-type is "application/json" will have this problem. It's odd.
You can return boolean if you need only to return value of success :
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody boolean upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
return true; //or false
}
Well, not really the best solution, but it solves the problem. I've created class, which has Map
inside and method for adding parameters into Map
. Also there is implemented method toString()
.
public class ExtJSJsonResponse {
/** Parameters to serialize to JSON */
private Map<String, Object> params = new HashMap<String, Object>();
/**
* Add arbitrary parameter for JSON serialization.
* Parameter will be serialized as {"key":"value"};
* @param key name of parameter
* @param value content of parameter
*/
@JsonIgnore
public void addParam(String key, Object value) {
params.put(key, value);
}
/**
* Gets all parameters. Also is annotated with <code>@JsonValue</code>.
* @return all params with keys as map
*/
@JsonValue
public Map<String, Object> getParams() {
return params;
}
/**
* Returns specified parameter by <code>key</code> as string "key":"value"
* @param key parameter key
* @return "key":"value" string or empty string when there is no parameter
* with specified key
*/
private String paramToString(String key) {
return params.containsKey(key)
? "\"" + key + "\":\"" + params.get(key) + "\""
: "";
}
/**
* Manually transforms map parameters to JSON string. Used when ExtJS fails
* to decode Jackson response. i.e. when uploading file.
* @return
*/
@Override
@JsonIgnore
public String toString() {
StringBuilder sb = new StringBuilder("{");
String delimiter = "";
for (String key : params.keySet()) {
sb.append(delimiter);
sb.append(paramToString(key));
delimiter = ",";
}
sb.append("}");
return sb.toString();
}
}
So when Uncaught Ext.Error: You're trying to decode an invalid JSON String
occurs, you simply do this
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody String upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
ExtJSJsonResponse response = new ExtJSJsonResponse();
// File processing
response.addParam("success", true);
response.addParam("message", "All OK");
return response.toString();
}
In other methods which doesn't have problem with serialization you can simply call return response;
and it will be automatically serialized.
Method toString()
will work only for simple classes such as String. For more complicated classes you'll have to change it.
I think you can use the "produces" attribute of Spring's @RequestMapping annotation:
@RequestMapping(value = "/upload", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE)
public @ResponseBody Map<String, Object> upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
// File processing
Map<String, Object> jsonResult = new HashMap<String, Object>();
jsonResult.put("success", Boolean.TRUE);
return jsonResult;
}
In config file, you should make this Content-Type available:
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<array>
<value>text/html</value>
<value>application/json</value>
</array>
</property>
</bean>
This is available in Spring 3.1.1.RELEASE, maybe in older versions it doesn't work.
精彩评论