diff --git a/spring-core-2/README.md b/spring-core-2/README.md index bcb1ce1fb4..b0997df1c7 100644 --- a/spring-core-2/README.md +++ b/spring-core-2/README.md @@ -15,4 +15,5 @@ This module contains articles about core Spring functionality - [Spring Events](https://www.baeldung.com/spring-events) - [Spring Null-Safety Annotations](https://www.baeldung.com/spring-null-safety-annotations) - [Using @Autowired in Abstract Classes](https://www.baeldung.com/spring-autowired-abstract-class) +- [Read HttpServletRequest Multiple Times](https://www.baeldung.com/reading-httpservletrequest-multiple-times-in-spring) - More articles: [[<-- prev]](/spring-core) \ No newline at end of file diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequest.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequest.java new file mode 100644 index 0000000000..b6d4905653 --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequest.java @@ -0,0 +1,37 @@ +package org.baeldung.cachedrequest; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.springframework.util.StreamUtils; + +public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { + + private byte[] cachedBody; + + public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { + super(request); + InputStream requestInputStream = request.getInputStream(); + this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new CachedBodyServletInputStream(this.cachedBody); + } + + @Override + public BufferedReader getReader() throws IOException { + // Create a reader from cachedContent + // and return it + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); + return new BufferedReader(new InputStreamReader(byteArrayInputStream)); + } +} \ No newline at end of file diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyServletInputStream.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyServletInputStream.java new file mode 100644 index 0000000000..a18de30788 --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyServletInputStream.java @@ -0,0 +1,43 @@ +package org.baeldung.cachedrequest; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +public class CachedBodyServletInputStream extends ServletInputStream { + + private InputStream cachedBodyInputStream; + + public CachedBodyServletInputStream(byte[] cachedBody) { + this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody); + } + + @Override + public boolean isFinished() { + try { + return cachedBodyInputStream.available() == 0; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return false; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + throw new UnsupportedOperationException(); + } + + @Override + public int read() throws IOException { + return cachedBodyInputStream.read(); + } +} diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/ContentCachingFilter.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/ContentCachingFilter.java new file mode 100644 index 0000000000..3cf61c0ee8 --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/ContentCachingFilter.java @@ -0,0 +1,23 @@ +package org.baeldung.cachedrequest; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Order(value = Ordered.HIGHEST_PRECEDENCE) +@Component +public class ContentCachingFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest); + filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse); + } +} diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/HttpRequestDemoConfig.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/HttpRequestDemoConfig.java new file mode 100644 index 0000000000..6dee069f29 --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/HttpRequestDemoConfig.java @@ -0,0 +1,37 @@ +package org.baeldung.cachedrequest; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.view.InternalResourceViewResolver; +import org.springframework.web.servlet.view.JstlView; + +@EnableWebMvc +@Configuration +public class HttpRequestDemoConfig implements WebMvcConfigurer { + + public HttpRequestDemoConfig() { + super(); + } + + // API + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + registry.addViewController("/sample.html"); + } + + @Bean + public ViewResolver viewResolver() { + final InternalResourceViewResolver bean = new InternalResourceViewResolver(); + + bean.setViewClass(JstlView.class); + bean.setPrefix("/WEB-INF/view/"); + bean.setSuffix(".jsp"); + + return bean; + } +} \ No newline at end of file diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/Person.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/Person.java new file mode 100644 index 0000000000..c9934f1eb9 --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/Person.java @@ -0,0 +1,44 @@ +package org.baeldung.cachedrequest; + +public class Person { + private String firstName; + + private String lastName; + + private int age; + + public Person(String firstName, String lastName, int age) { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", age=" + age + '}'; + } +} diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/PersonController.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PersonController.java new file mode 100644 index 0000000000..6f241b3fb3 --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PersonController.java @@ -0,0 +1,26 @@ +package org.baeldung.cachedrequest; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PersonController { + + @PostMapping(value = "/person") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void printPerson(@RequestBody Person person) { + + System.out.println("In Demo Controller. Person " + "is : " + person); + } + + @GetMapping(value = "/person") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void getPerson() { + + System.out.println("In Demo Controller get method."); + } +} \ No newline at end of file diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/PrintRequestContentFilter.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PrintRequestContentFilter.java new file mode 100644 index 0000000000..7a87ac7e4c --- /dev/null +++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PrintRequestContentFilter.java @@ -0,0 +1,27 @@ +package org.baeldung.cachedrequest; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +@Order(Ordered.LOWEST_PRECEDENCE) +@Component +public class PrintRequestContentFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + InputStream inputStream = httpServletRequest.getInputStream(); + byte[] body = StreamUtils.copyToByteArray(inputStream); + System.out.println("In PrintRequestContentFilter." + " Request body is: " + new String(body)); + filterChain.doFilter(httpServletRequest, httpServletResponse); + } +} diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequestUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequestUnitTest.java new file mode 100644 index 0000000000..1fe9dd4c82 --- /dev/null +++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequestUnitTest.java @@ -0,0 +1,62 @@ +package org.baeldung.cachedrequest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.util.StreamUtils; + +import junit.framework.TestCase; + +@RunWith(MockitoJUnitRunner.class) +public class CachedBodyHttpServletRequestUnitTest extends TestCase { + + private CachedBodyServletInputStream servletInputStream; + + @After + public void cleanUp() throws IOException { + if (null != servletInputStream) { + servletInputStream.close(); + } + } + + @Test + public void testGivenHttpServletRequestWithBody_whenCalledGetInputStream_ThenGetsServletInputStreamWithSameBody() throws IOException { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + MockHttpServletRequest mockeddHttpServletRequest = new MockHttpServletRequest(); + mockeddHttpServletRequest.setContent(cachedBody); + CachedBodyHttpServletRequest request = new CachedBodyHttpServletRequest(mockeddHttpServletRequest); + + // when + InputStream inputStream = request.getInputStream(); + + // then + assertEquals(new String(cachedBody), new String(StreamUtils.copyToByteArray(inputStream))); + } + + @Test + public void testGivenHttpServletRequestWithBody_whenCalledGetReader_ThenGetBufferedReaderWithSameBody() throws IOException { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + MockHttpServletRequest mockeddHttpServletRequest = new MockHttpServletRequest(); + mockeddHttpServletRequest.setContent(cachedBody); + CachedBodyHttpServletRequest request = new CachedBodyHttpServletRequest(mockeddHttpServletRequest); + + // when + BufferedReader bufferedReader = request.getReader(); + + // then + String line = ""; + StringBuilder builder = new StringBuilder(); + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + assertEquals(new String(cachedBody), builder.toString()); + } +} diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyServletInputStreamUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyServletInputStreamUnitTest.java new file mode 100644 index 0000000000..d7000d91ee --- /dev/null +++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyServletInputStreamUnitTest.java @@ -0,0 +1,98 @@ +package org.baeldung.cachedrequest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ReadListener; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.util.StreamUtils; + +import junit.framework.TestCase; + +@RunWith(MockitoJUnitRunner.class) +public class CachedBodyServletInputStreamUnitTest extends TestCase { + + private CachedBodyServletInputStream servletInputStream; + + @After + public void cleanUp() throws IOException { + if (null != servletInputStream) { + servletInputStream.close(); + } + } + + @Test + public void testGivenServletInputStreamCreated_whenCalledisFinished_Thenfalse() { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + servletInputStream = new CachedBodyServletInputStream(cachedBody); + + // when + boolean finished = servletInputStream.isFinished(); + + // then + assertFalse(finished); + } + + @Test + public void testGivenServletInputStreamCreatedAndBodyRead_whenCalledisFinished_ThenTrue() throws IOException { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + servletInputStream = new CachedBodyServletInputStream(cachedBody); + StreamUtils.copyToByteArray(servletInputStream); + + // when + boolean finished = servletInputStream.isFinished(); + + // then + assertTrue(finished); + } + + @Test + public void testGivenServletInputStreamCreatedAndBodyRead_whenCalledIsReady_ThenTrue() throws IOException { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + servletInputStream = new CachedBodyServletInputStream(cachedBody); + + // when + boolean ready = servletInputStream.isReady(); + + // then + assertTrue(ready); + } + + @Test + public void testGivenServletInputStreamCreated_whenCalledIsRead_ThenReturnsBody() throws IOException { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + servletInputStream = new CachedBodyServletInputStream(cachedBody); + + // when + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + int len = 0; + byte[] buffer = new byte[1024]; + while ((len = servletInputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, len); + } + + // then + assertEquals(new String(cachedBody), new String(byteArrayOutputStream.toByteArray())); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGivenServletInputStreamCreated_whenCalledIsRead_ThenThrowsException() throws IOException { + // Given + byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes(); + servletInputStream = new CachedBodyServletInputStream(cachedBody); + + // when + servletInputStream.setReadListener(Mockito.mock(ReadListener.class)); + + } + +} diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/ContentCachingFilterUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/ContentCachingFilterUnitTest.java new file mode 100644 index 0000000000..057f1ac627 --- /dev/null +++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/ContentCachingFilterUnitTest.java @@ -0,0 +1,39 @@ +package org.baeldung.cachedrequest; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import junit.framework.TestCase; + +@RunWith(MockitoJUnitRunner.class) +public class ContentCachingFilterUnitTest extends TestCase { + + @InjectMocks + private ContentCachingFilter filterToTest; + + @Test + public void testGivenHttpRequest_WhenDoFilter_thenCreatesRequestWrapperObject() throws IOException, ServletException { + // Given + MockHttpServletRequest mockedRequest = new MockHttpServletRequest(); + MockHttpServletResponse mockedResponse = new MockHttpServletResponse(); + FilterChain mockedFilterChain = Mockito.mock(FilterChain.class); + + // when + filterToTest.doFilter(mockedRequest, mockedResponse, mockedFilterChain); + + // then + Mockito.verify(mockedFilterChain, Mockito.times(1)) + .doFilter(Mockito.any(CachedBodyHttpServletRequest.class), Mockito.any(MockHttpServletResponse.class)); + } + +} diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/PersonControllerIntegrationTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PersonControllerIntegrationTest.java new file mode 100644 index 0000000000..77d6a816e3 --- /dev/null +++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PersonControllerIntegrationTest.java @@ -0,0 +1,46 @@ +package org.baeldung.cachedrequest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; + +import javax.print.attribute.PrintRequestAttribute; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { HttpRequestDemoConfig.class, ContentCachingFilter.class, PrintRequestAttribute.class }) +@AutoConfigureMockMvc +public class PersonControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void whenValidInput_thenCreateBook() throws IOException, Exception { + // assign - given + Person book = new Person("sumit", "abc", 100); + + // act - when + ResultActions result = mockMvc.perform(post("/person").accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(book))); + + // assert - then + result.andExpect(status().isNoContent()); + } + +} diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/PrintRequestContentFilterUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PrintRequestContentFilterUnitTest.java new file mode 100644 index 0000000000..ca2d5c600b --- /dev/null +++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PrintRequestContentFilterUnitTest.java @@ -0,0 +1,40 @@ +package org.baeldung.cachedrequest; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import junit.framework.TestCase; + +@RunWith(MockitoJUnitRunner.class) +public class PrintRequestContentFilterUnitTest extends TestCase { + + @InjectMocks + private PrintRequestContentFilter filterToTest; + + @Test + public void testGivenHttpRequest_WhenDoFilter_thenReadsBody() throws IOException, ServletException { + // Given + MockHttpServletRequest mockedRequest = new MockHttpServletRequest(); + MockHttpServletResponse mockedResponse = new MockHttpServletResponse(); + FilterChain mockedFilterChain = Mockito.mock(FilterChain.class); + CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(mockedRequest); + + // when + filterToTest.doFilter(cachedBodyHttpServletRequest, mockedResponse, mockedFilterChain); + + // then + Mockito.verify(mockedFilterChain, Mockito.times(1)) + .doFilter(cachedBodyHttpServletRequest, mockedResponse); + } + +}