Background
Let's say we have a #RestController with the following method (Spring Boot 1.3.5.RELEASE):
#RequestMapping(value = "/helloworld", method = RequestMethod.POST)
public Map<String, String> helloWorld(#RequestBody Map<String, String> m) {
m.put("Hello", "2");
m.put("World", "1");
return m;
}
And a #Test using TestRestTemplate:
RestTemplate restTemplate = new TestRestTemplate();
#Test
public void testHelloWorld() {
Map<String, String> request = new HashMap<>();
request.put("Hello", "1");
request.put("World", "2");
Map<String, String> respons = this.restTemplate.postForObject("/helloworld", request, Map.class);
}
Question
How can one print/log the actual request/respons being sent/received?
I.e how to print/log the serialized versions of the request/respons?
This is availible on the level of request filters:
public class YourCustomFilter implements Filter {
#Override
public void destroy() {
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// your code here
}
#Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// your code here
}
}
But, the question is: Would you really like to do this and because of what? Remember, that this is really error prone.
You can use aspect to log request and response
example snippet of request:
#Aspect
#Component
public class InputLoggerAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(InputLoggerAspect.class);
#Autowired
private ObjectMapper objectMapper;
#Before(value = "valueToPointCut")
public void before(JoinPoint pointcut) throws Exception {
Object[] args = pointcut.getArgs();
for (Object object : args) {
LOGGER.info("{}:{}", object.getClass(), objectMapper.writeValueAsString(object));
}
}
}
Related
How to properly cover Filter with JUnit?
#SlingFilter(order = -700, scope = SlingFilterScope.REQUEST)
public class LoggingFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain filterChain) throws IOException, ServletException {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
logger.debug("request for {}, with selector {}", slingRequest
.getRequestPathInfo().getResourcePath(), slingRequest
.getRequestPathInfo().getSelectorString());
filterChain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
}
You can use below code for your testing with Junit-5
#ExtendWith(MockitoExtension.class)
public class LoggingFilterTest{
#InjectMocks
private LoggingFilter loggingFilter;
#Mock
private ServletRequest request
#Mock
private ServletResponse response
#Mock
private FilterChain filterChain
#Mock
private RequestPathInfo requestPathInfo;
#Test
public void testDoFilter() throws IOException, ServletException{
Mockito.when(request.getResourcePath()).thenReturn(requestPathInfo);
Mockito.when(requestPathInfo.getResourcePath()).thenReturn("/testPath", "selectorString");
Mockito.doNothing().when(filterChain).doFilter(Mockito.eq(request), Mockito.eq(response));
loggingFilter.doFilter(request, response, filterChain);
Mockito.verify(filterChain, times(1)).doFilter(Mockito.eq(request), Mockito.eq(response));
}
}
If you are using junit4 then change #ExtendWith(MockitoExtension.class) to #RunWith(MockitoJUnitRunner.class)
Invoke doFilter passing the mock ServletRequest, ServletResponse and FilterChain as its parameters.
#Test
public void testDoFilter() {
LoggingFilter filterUnderTest = new LoggingFilter();
MockFilterChain mockChain = new MockFilterChain();
MockServletRequest req = new MockServletRequest("/test.jsp");
MockServletResponse rsp = new MockServletResponse();
filterUnderTest.doFilter(req, rsp, mockChain);
assertEquals("/test.jsp",rsp.getLastRedirect());
}
In practice, you'll want to move the setup into an #Before setUp() method, and write more #Test methods to cover every possible execution path.
And you'd probably use a mocking framework like JMock or Mockito to create mocks, rather than the hypothetical MockModeService etc. I've used here.
This is a unit testing approach, as opposed to an integration test. You are only exercising the unit under test (and the test code).
If you use AEM Mocks with Junit5, then it could look something like this.
#ExtendWith(AemContextExtension.class)
class SimpleFilterTest {
private static final AemContext context = new AemContext(ResourceResolverType.RESOURCERESOLVER_MOCK);
private static final String RESOURCE_PATH = "/content/test";
private static final String SELECTOR_STRING = "selectors";
private static SimpleFilter simpleFilter;
private static FilterChain filterChain;
#BeforeAll
static void setup() {
simpleFilter = context.registerService(SimpleFilter.class, new SimpleFilter());
filterChain = context.registerService(FilterChain.class, new MockFilterChain());
}
#Test
#DisplayName("GIVEN the request, WHEN is executed, THEN request should be filtered and contain corresponding header")
void testDoFilter() throws ServletException, IOException {
context.requestPathInfo().setResourcePath(RESOURCE_PATH);
context.requestPathInfo().setSelectorString(SELECTOR_STRING);
simpleFilter.doFilter(context.request(), context.response(), filterChain);
assertEquals("true", context.response().getHeader("filtered"));
}
}
Mock
public class MockFilterChain implements FilterChain {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, ServletException {
// do nothing
}
}
Some simple filter
#Component(service = Filter.class)
#SlingServletFilter(scope = SlingServletFilterScope.REQUEST)
#ServiceRanking(-700)
public class SimpleFilter implements Filter {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
log.debug("request for {}, with selector {}", slingRequest.getRequestPathInfo().getResourcePath(),
slingRequest.getRequestPathInfo().getSelectorString());
final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response;
slingResponse.setHeader("filtered", "true");
filterChain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
i am studying spring 5 and i can not use #RequestMapping annotation and don't know why
#RequestMapping includes #Component annotation so I just thought I can use that
initRequest includes URL parameter by string
i just expected initRequest(/hello) parameter binds URL
here is my code
public class SimpleControllerTest extends AbstractDispatcherServletTest {
#Test
public void helloSimpleController() throws ServletException, IOException {
setClasses(HelloController.class);
initRequest("/hello").addParameter("name", "spring");
runService();
assertModel("message", "Hello spring");
assertViewName("/WEB-INF/view/hello.jsp");
}
#Test(expected=Exception.class)
public void noParameterHelloSimpleController() throws ServletException, IOException {
setClasses(HelloController.class);
initRequest("/hello");
runService();
}
#Component("/hello")
//#RequestMapping("/hello")
static class HelloController extends SimpleController {
public HelloController() {
this.setRequiredParams(new String[] {"name"});
this.setViewName("/WEB-INF/view/hello.jsp");
}
public void control(Map<String, String> params, Map<String, Object> model) throws Exception {
model.put("message", "Hello " + params.get("name"));
}
}
static abstract class SimpleController implements Controller {
private String[] requiredParams;
private String viewName;
public void setRequiredParams(String[] requiredParams) {
this.requiredParams = requiredParams;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
final public ModelAndView handleRequest(HttpServletRequest req,
HttpServletResponse res) throws Exception {
...
}
public abstract void control(Map<String, String> params, Map<String, Object> model) throws Exception;
}
}
You need to work on your Spring basics. Your understanding of which annotations do what is incorrect and incomplete. The following links provide good knowledge on these. Go through these, revise your code, and you will solve this problem without needing help.
Spring Framework Annotations
Spring Annotations - JournalDev
I am trying to add junit test case for my Spring Boot OncePerRequestFilter shouldNotFilter method logic. The logic works fine with real-time REST calls but junit case is failing. Any idea?.
Here is test code.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringFilterTest {
#Test
public void getHealthTest() throws Exception {
standaloneSetup(new PersonController()).addFilter(new SkipFilter()).build().perform(get("/health")).andExpect(status().isOk());
}
#Test
public void getPersonTest() throws Exception {
standaloneSetup(new PersonController()).addFilter(new SkipFilter()).build().perform(get("/person")).andExpect(status().isAccepted());
}
private class SkipFilter extends OncePerRequestFilter {
private Set<String> skipUrls = new HashSet<>(Arrays.asList("/health"));
private AntPathMatcher pathMatcher = new AntPathMatcher();
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(request, response);
response.setStatus(HttpStatus.ACCEPTED.value());
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return skipUrls.stream().anyMatch(p -> pathMatcher.match(p, request.getServletPath()));
}
}
#RestController
#RequestMapping(value = "/")
private static class PersonController {
#GetMapping("person")
public void getPerson() {
}
#GetMapping("health")
public void getHealth() {
}
}
}
I am expecting both of junit #Test cases to be successful but health one is always failing(its using Filter).
Incase, if you want to replicate below is complete repo code.
https://github.com/imran9m/spring-filter-test
Below Expression evaluates to false with request.getServletPath() when /health
skipUrls.stream().anyMatch(p -> pathMatcher.match(p, request.getServletPath()));
Change to request.getRequestURI() to get the uri and below condition matches the path
skipUrls.stream().anyMatch(p -> pathMatcher.match(p, request.getRequestURI()));
I have a Java Spring controller.
I want to escape all quotes in my request (sanitize it for using it in SQL queries for example).
Is there a way to do that with Spring ?
Example :
#RequestMapping(method = RequestMethod.POST)
public List<String[]> myEndpoint(#RequestBody Map<String, String> params, #AuthenticationPrincipal Account connectedUser) throws Exception{
return myService.runQuery(params, connectedUser);
}
If you want to validate all your request parameters in controllers, you can use custom validators. For Complete info, check Complete Example
Brief Overview:
Validator Implementation
#Component
public class YourValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(YourPojoType.class);
}
#Override
public void validate(Object target, Errors errors) {
if (target instanceof YourPojoType) {
YourPojoType req = (YourPojoType) target;
Map<String, String> params = req.getParams();
//Do your validations.
//if any validation failed,
errors.rejectValue("yourFieldName", "YourCustomErrorCode", "YourCustomErrorMessage");
}
}
}
Controller
#RestController
public class YourController{
#Autowired
private YourValidator validator;
#RequestMapping(method = RequestMethod.POST)
public List<String[]> myEndpoint(#Valid YourPojoType req, BindingResult result, #AuthenticationPrincipal Account connectedUser) throws Exception{
if (result.hasErrors()) {
//throw exception
}
return myService.runQuery(params, connectedUser);
}
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
}
I have some spring #RestControllers methods that I would like to inject with a value that comes with every request as a request attribute(containing the user) something like:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
// Option 1 get user from request attribute as prop somehow
private String userId = "user1";
// Option 2 inject into method using aspect or something else
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs() throws ResourceNotFoundException {
// currentUser is injected
this.getJobs(currentUser);
}
I know I can do that:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException {
String currentUser = null;
if (request.getAttribute("subject") != null) {
currentUser = request.getAttribute("subject").toString();
}
this.getJobs(currentUser);
}
But that would require me to add this code at every method in my program, which seems to me, to be a really bad practice.
Is there a way to achieve what I want?
If the answer do require aspect, a code example will be much appreciated since I only read about it, but never actually did something with aspect.
Update
The code i suggested can be simplified using this:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(#Value("#{request.getAttribute('subject')}" String currentUser) throws ResourceNotFoundException {
this.getJobs(currentUser);
}
But still require me to add that parameter at every method.
Can this parameter be injected to every method somehow?
You could use a Filter to populate a ThreadLocal<String> variable that stores that attribute:
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
ContextHolder.setSubject(request.getAttribute('subject'));
chain.doFilter(request, response);
}
#Override
public void destroy() {
ContextHolder.removeSubject();
}
}
public class ContextHolder {
private static final ThreadLocal<String> SUBJECT = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return "empty";
}
};
public static void setSubject(String subject) {
SUBJECT.set(subject);
}
public static String getSubject() {
return SUBJECT.get();
}
public static void removeSubject() {
SUBJECT.remove();
}
}
The filter will be configured to intercept all requests and populate the SUBJECT variable. By using a ThreadLocal, you make sure that each thread has it's own subject value. You can now get that value anywhere in your application by calling ContextHolder.getSubject():
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException {
this.getJobs(ContextHolder.getSubject());
}
You will also have to register the Filter in the web.xml file:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
In case you had multiple attributes, you could use a ThreadLocal<Map<String, String>> variable instead.
Simply just add #ResuestAttribute in your rest contorller
#RestController
#RequestMapping(path="/yourpath")
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity getAll(
#RequestAttribute(value = "yourAttribute") Object
yourAttribute......
If you really want to know about attributes then you should check out spring's #RequestParam annotation. You'd use it like this:
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(#RequestParam("subject") String currentUser) throws ResourceNotFoundException {
this.getJobs(currentUser);
}