提交 1cd0a975 编写于 作者: A Arjen Poutsma

SPR-6467 - Allow ContentNegotiatingViewResolver to be strict ant return a 406 if no view found

上级 0cb9271b
...@@ -31,6 +31,7 @@ import javax.activation.FileTypeMap; ...@@ -31,6 +31,7 @@ import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap; import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.core.OrderComparator; import org.springframework.core.OrderComparator;
...@@ -114,6 +115,8 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -114,6 +115,8 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
private boolean favorParameter = false; private boolean favorParameter = false;
private String parameterName = "format"; private String parameterName = "format";
private boolean useNotAcceptableStatusCode = false;
private boolean ignoreAcceptHeader = false; private boolean ignoreAcceptHeader = false;
...@@ -125,7 +128,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -125,7 +128,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
private List<ViewResolver> viewResolvers; private List<ViewResolver> viewResolvers;
public void setOrder(int order) { public void setOrder(int order) {
this.order = order; this.order = order;
} }
...@@ -174,6 +176,20 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -174,6 +176,20 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
this.ignoreAcceptHeader = ignoreAcceptHeader; this.ignoreAcceptHeader = ignoreAcceptHeader;
} }
/**
* Indicates whether a {@link HttpServletResponse#SC_NOT_ACCEPTABLE 406 Not Acceptable} status code should be
* returned if no suitable view can be found.
*
* <p>Default is {@code false}, meaning that this view resolver returns {@code null} for
* {@link #resolveViewName(String, Locale)} when an acceptable view cannot be found. This will allow for view
* resolvers chaining. When this property is set to {@code true},
* {@link #resolveViewName(String, Locale)} will respond with a view that sets the response status to
* {@code 406 Not Acceptable} instead.
*/
public void setUseNotAcceptableStatusCode(boolean useNotAcceptableStatusCode) {
this.useNotAcceptableStatusCode = useNotAcceptableStatusCode;
}
/** /**
* Sets the mapping from file extensions to media types. * Sets the mapping from file extensions to media types.
* <p>When this mapping is not set or when an extension is not present, this view resolver * <p>When this mapping is not set or when an extension is not present, this view resolver
...@@ -337,7 +353,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -337,7 +353,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
Collections.sort(requestedMediaTypes); Collections.sort(requestedMediaTypes);
} }
SortedMap<MediaType, View> views = new TreeMap<MediaType, View>();
List<View> candidateViews = new ArrayList<View>(); List<View> candidateViews = new ArrayList<View>();
for (ViewResolver viewResolver : this.viewResolvers) { for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale); View view = viewResolver.resolveViewName(viewName, locale);
...@@ -349,6 +364,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -349,6 +364,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
candidateViews.addAll(this.defaultViews); candidateViews.addAll(this.defaultViews);
} }
SortedMap<MediaType, View> views = new TreeMap<MediaType, View>();
for (View candidateView : candidateViews) { for (View candidateView : candidateViews) {
String contentType = candidateView.getContentType(); String contentType = candidateView.getContentType();
if (StringUtils.hasText(contentType)) { if (StringUtils.hasText(contentType)) {
...@@ -373,11 +389,10 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -373,11 +389,10 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
return view; return view;
} }
else { else {
return null; return useNotAcceptableStatusCode ? new NotAcceptableView() : null;
} }
} }
/** /**
* Inner class to avoid hard-coded JAF dependency. * Inner class to avoid hard-coded JAF dependency.
*/ */
...@@ -421,4 +436,15 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport ...@@ -421,4 +436,15 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
} }
} }
private static class NotAcceptableView implements View {
public String getContentType() {
return null;
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
}
} }
...@@ -30,6 +30,7 @@ import org.junit.Test; ...@@ -30,6 +30,7 @@ import org.junit.Test;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
...@@ -305,4 +306,58 @@ public class ContentNegotiatingViewResolverTests { ...@@ -305,4 +306,58 @@ public class ContentNegotiatingViewResolverTests {
verify(viewResolverMock, viewMock); verify(viewResolverMock, viewMock);
} }
@Test
public void resolveViewNoMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9");
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
ViewResolver viewResolverMock = createMock(ViewResolver.class);
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
View viewMock = createMock("application_xml", View.class);
String viewName = "view";
Locale locale = Locale.ENGLISH;
expect(viewResolverMock.resolveViewName(viewName, locale)).andReturn(viewMock);
expect(viewMock.getContentType()).andReturn("application/pdf");
replay(viewResolverMock, viewMock);
View result = viewResolver.resolveViewName(viewName, locale);
assertNull("Invalid view", result);
verify(viewResolverMock, viewMock);
}
@Test
public void resolveViewNoMatchUseUnacceptableStatus() throws Exception {
viewResolver.setUseNotAcceptableStatusCode(true);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9");
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
ViewResolver viewResolverMock = createMock(ViewResolver.class);
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
View viewMock = createMock("application_xml", View.class);
String viewName = "view";
Locale locale = Locale.ENGLISH;
expect(viewResolverMock.resolveViewName(viewName, locale)).andReturn(viewMock);
expect(viewMock.getContentType()).andReturn("application/pdf");
replay(viewResolverMock, viewMock);
View result = viewResolver.resolveViewName(viewName, locale);
assertNotNull("Invalid view", result);
MockHttpServletResponse response = new MockHttpServletResponse();
result.render(null, request, response);
assertEquals("Invalid status code set", 406, response.getStatus());
verify(viewResolverMock, viewMock);
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册