From 01a9dd97721c93fe80d8fd35bd5dc8de649b3595 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 15 May 2012 18:06:14 -0400 Subject: [PATCH] Add option to set Content-Length in JSON Views MappingJackson2JsonView and MappingJacksonJsonView now provide an option that will set the Content-Length header of JSON responses. Use of the option implies buffering of the response and it must be enabled explicitly. Issue: SPR-7866 --- .../view/json/MappingJackson2JsonView.java | 21 ++++++++++++++-- .../view/json/MappingJacksonJsonView.java | 24 ++++++++++++++++--- ...java => MappingJackson2JsonViewTests.java} | 6 ++++- ....java => MappingJacksonJsonViewTests.java} | 8 +++++-- src/dist/changelog.txt | 3 ++- 5 files changed, 53 insertions(+), 9 deletions(-) rename spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/{MappingJackson2JsonViewTest.java => MappingJackson2JsonViewTests.java} (97%) rename spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/{MappingJacksonJsonViewTest.java => MappingJacksonJsonViewTests.java} (96%) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java index ea94f287c9..d62149c7d7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java @@ -16,6 +16,8 @@ package org.springframework.web.servlet.view.json; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -71,6 +73,7 @@ public class MappingJackson2JsonView extends AbstractView { private boolean disableCaching = true; + private boolean updateContentLength = false; /** * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. @@ -199,6 +202,15 @@ public class MappingJackson2JsonView extends AbstractView { this.disableCaching = disableCaching; } + /** + * Whether to update the 'Content-Length' header of the response. When set to + * {@code true}, the response is buffered in order to determine the content + * length and set the 'Content-Length' header of the response. + *

The default setting is {@code false}. + */ + public void setUpdateContentLength(boolean updateContentLength) { + this.updateContentLength = updateContentLength; + } @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { @@ -215,9 +227,10 @@ public class MappingJackson2JsonView extends AbstractView { protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream(); + Object value = filterModel(model); - JsonGenerator generator = - this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding); + JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding); // A workaround for JsonGenerators not applying serialization features // https://github.com/FasterXML/jackson-databind/issues/12 @@ -229,6 +242,10 @@ public class MappingJackson2JsonView extends AbstractView { generator.writeRaw("{} && "); } this.objectMapper.writeValue(generator, value); + + if (this.updateContentLength) { + writeToResponse(response, (ByteArrayOutputStream) stream); + } } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java index c511a38bf5..a1805e244a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.web.servlet.view.json; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -73,6 +75,7 @@ public class MappingJacksonJsonView extends AbstractView { private boolean disableCaching = true; + private boolean updateContentLength = false; /** * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. @@ -201,6 +204,16 @@ public class MappingJacksonJsonView extends AbstractView { this.disableCaching = disableCaching; } + /** + * Whether to update the 'Content-Length' header of the response. When set to + * {@code true}, the response is buffered in order to determine the content + * length and set the 'Content-Length' header of the response. + *

The default setting is {@code false}. + */ + public void setUpdateContentLength(boolean updateContentLength) { + this.updateContentLength = updateContentLength; + } + @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { @@ -217,9 +230,10 @@ public class MappingJacksonJsonView extends AbstractView { protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream(); + Object value = filterModel(model); - JsonGenerator generator = - this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding); + JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding); // A workaround for JsonGenerators not applying serialization features // https://github.com/FasterXML/jackson-databind/issues/12 @@ -231,6 +245,10 @@ public class MappingJacksonJsonView extends AbstractView { generator.writeRaw("{} && "); } this.objectMapper.writeValue(generator, value); + + if (this.updateContentLength) { + writeToResponse(response, (ByteArrayOutputStream) stream); + } } /** diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTest.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java similarity index 97% rename from spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTest.java rename to spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java index dad748d6d6..c3196a6f58 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTest.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTests.java @@ -59,7 +59,7 @@ import com.fasterxml.jackson.databind.ser.Serializers; * @author Arjen Poutsma * @author Rossen Stoyanchev */ -public class MappingJackson2JsonViewTest { +public class MappingJackson2JsonViewTests { private MappingJackson2JsonView view; @@ -94,6 +94,7 @@ public class MappingJackson2JsonViewTest { model.put("bindingResult", createMock("binding_result", BindingResult.class)); model.put("foo", "bar"); + view.setUpdateContentLength(true); view.render(model, request, response); assertEquals("no-cache", response.getHeader("Pragma")); @@ -104,6 +105,7 @@ public class MappingJackson2JsonViewTest { String jsonResult = response.getContentAsString(); assertTrue(jsonResult.length() > 0); + assertEquals(jsonResult.length(), response.getContentLength()); validateResult(); } @@ -137,9 +139,11 @@ public class MappingJackson2JsonViewTest { model.put("bindingResult", createMock("binding_result", BindingResult.class)); model.put("foo", bean); + view.setUpdateContentLength(true); view.render(model, request, response); assertTrue(response.getContentAsString().length() > 0); + assertEquals(response.getContentAsString().length(), response.getContentLength()); validateResult(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTest.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTests.java similarity index 96% rename from spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTest.java rename to spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTests.java index 9930c511d4..9caf042842 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTest.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/MappingJacksonJsonViewTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,7 +52,7 @@ import org.springframework.validation.BindingResult; * @author Jeremy Grelle * @author Arjen Poutsma */ -public class MappingJacksonJsonViewTest { +public class MappingJacksonJsonViewTests { private MappingJacksonJsonView view; @@ -87,6 +87,7 @@ public class MappingJacksonJsonViewTest { model.put("bindingResult", createMock("binding_result", BindingResult.class)); model.put("foo", "bar"); + view.setUpdateContentLength(true); view.render(model, request, response); assertEquals("no-cache", response.getHeader("Pragma")); @@ -97,6 +98,7 @@ public class MappingJacksonJsonViewTest { String jsonResult = response.getContentAsString(); assertTrue(jsonResult.length() > 0); + assertEquals(jsonResult.length(), response.getContentLength()); validateResult(); } @@ -130,9 +132,11 @@ public class MappingJacksonJsonViewTest { model.put("bindingResult", createMock("binding_result", BindingResult.class)); model.put("foo", bean); + view.setUpdateContentLength(true); view.render(model, request, response); assertTrue(response.getContentAsString().length() > 0); + assertEquals(response.getContentAsString().length(), response.getContentLength()); validateResult(); } diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index fd36db3ad9..1fe61e300c 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -25,7 +25,8 @@ Changes in version 3.2 M1 * support access to all URI vars via @PathVariable Map * add "excludedExceptions" property to SimpleUrlHandlerMapping * add CompositeRequestCondition for use with multiple custom request mapping conditions -* add option to configure custom MessageCodesResolver through WebMvcConfigurer +* add ability to configure custom MessageCodesResolver through the MVC Java config +* add option in MappingJacksonJsonView for setting the Content-Length header Changes in version 3.1.1 (2012-02-16) -------------------------------------