I have a normal spring #Controller which takes an URL-encoded string as parameter:
#RequestMapping(value = "/wechat/browser", method = GET)
public void askWeChatWhoTheUserIs(#RequestParam(name = "origin") String origin,
HttpServletResponse response) throws IOException {
//omitted codes
}
When I debug the spring boot application and test the endpoint with browser:
curl http://localhost:8080/wechat/browser\?origin\=http%3A%2F%2Fwww.example.com%2Findex.html%3Fa%3Db%23%2Froute
The origin got decoded automatically and equal to http://www.example.com/index.html?a=b#/route
But when I wrote a spring mvc test:
#RunWith(SpringRunner.class)
#WebMvcTest(WeChatOauthController.class)
public class WeChatOauthControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void itShouldRedirectToWeChatToFinishOauthProtocol() throws Exception {
String origin = "http://www.example.com/index.html?a=b#/route";
String encodedOrigin = URLEncoder.encode(origin, "UTF-8");
this.mvc.perform(get("/wechat/browser")
.param("origin", encodedOrigin))
.andDo(print())
//omitted codes
}
}
When I debug this test and the controller, the origin was not decoded this time. Just wondering why it behaves differently in these two cases.
When supplying a request parameter with the Spring MVC Test framework, there is no need to manually encode the parameter's value since there is no physical HTTP request.
So, just use the original raw value in your test, and it should work fine.
In other words, use this:
#RunWith(SpringRunner.class)
#WebMvcTest(WeChatOauthController.class)
public class WeChatOauthControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void itShouldRedirectToWeChatToFinishOauthProtocol() throws Exception {
this.mvc.perform(get("/wechat/browser")
.param("origin", "http://www.example.com/index.html?a=b#/route"))
.andDo(print())
//omitted codes
}
}
You can use this method , thus there will be proper decoding
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WeChatOauthControllerTest {
#LocalServerPort
private int port;
TestRestTemplate restTemplate = new TestRestTemplate();
#Test
public void testAmpersandEncoded(){
ResponseEntity<String> response =
restTemplate.exchange(createURI("%26"),HttpMethod.GET, null, String.class);
assertEquals(response.getStatusCode(), HttpStatus.OK);
}
private URI createURI(String param){
URI uri = null;
String url = "http://localhost:"+ port +"/location?query=" + param;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
log.error(e.getMessage());
}
return uri;
}
Related
I thought this is a standard configuration. But I get a 404 back. Where else should I configure Spring Boot ?
#RestController
#RequestMapping("/api")
public class TransactionStatisticsController {
public static final Logger logger = LoggerFactory.getLogger(TransactionStatisticsController.class);
#RequestMapping(value = "/transactions",
method = RequestMethod.POST)
public ResponseEntity sendTransaction(#RequestBody Transaction request) {
logger.info( request.toString());
return new ResponseEntity(HttpStatus.OK);
}
}
This is my test.
#JsonTest
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public class TransactionStatisticsRestTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private JacksonTester<Transaction> json;
private static Transaction transaction;
#BeforeClass
public static void createTransaction(){
BigDecimal amount = new BigDecimal(12.3343);
transaction = new Transaction(amount.toString(),
"2010-10-02T12:23:23Z");
}
#Test
public void getTransactionStatus() throws Exception {
final String transactionJson = json.write(transaction).getJson();
mockMvc
.perform(post("/api/transactions")
.content(transactionJson)
.contentType(APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsBytes(transaction);
}
}
Request being made is
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/transactions
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
Body = {"amount":"12.3343000000000007077005648170597851276397705078125","timestamp":"2010-10-02T12:23:23Z[UTC]"}
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Update : I added a component scan pointing to a base package. I don't see that error now. Please see the comments where there is an answer.
As in the comment section ,there was only requirement was to bind a component scan base package location .
#Component scan -->Configures component scanning directives for use with #Configuration classes. Provides support parallel with Spring XML's element.
Either basePackageClasses() or basePackages() (or its alias value()) may be specified to define specific packages to scan. If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
Please share your project folder architecture. It might be possible that your controller package is out of the main class package. That's why it is showing 404.
This code :
#RestController
#RequestMapping("/api")
public class TransactionStatisticsController {
public static final Logger logger = LoggerFactory.getLogger(TransactionStatisticsController.class);
#RequestMapping(value = "/transactions",
method = RequestMethod.POST)
public ResponseEntity sendTransaction(#RequestBody Transaction request) {
logger.info( request.toString());
return new ResponseEntity(HttpStatus.OK);
}
}
This should be into your main package where
#SpringBootApplication
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}
this main class resides.
I hope, this will help.
Seems using #JsonTest does not even allow to load Application Context, results mapping is not loaded and its throw 404 so #JsonTest is not a replacement for #SpringBootTest, it is a way to easily test json serialization/de-serialization.
As per documentation:
you can use the #JsonTest annotation. #JsonTest auto-configures the
available supported JSON mapper, which can be one of the following
libraries:
Jackson ObjectMapper, any #JsonComponent beans and any Jackson Modules
Gson
Jsonb
If by using Gson and removing #JsonTest your test run fine..(add Gson Dependency in pom)
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public class DemoKj01ApplicationTests {
#Autowired
private MockMvc mockMvc;
private static Transaction transaction;
#BeforeClass
public static void createTransaction(){
BigDecimal amount = new BigDecimal(12.3343);
transaction = new Transaction(amount.toString(),
"2010-10-02T12:23:23Z");
}
#Test
public void getTransactionStatus() throws Exception {
//final String transactionJson = json.write(transaction).getJson();
Gson gson = new Gson();
String jsonRequest = gson.toJson(transaction);
mockMvc
.perform(post("/api/transactions")
.content(jsonRequest)
.contentType(APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
It is beacause of the trailing slas in #RequestMapping(value = "/transactions/", method = RequestMethod.POST)
Remove it and it will be ok : value = "/transactions/" => value = "/transactions"
I am trying to update an audit entry using the response body advice but as far as I can tell it never gets executed. I see the bean in the logs:
{"timestamp":"2018-08-21T15:48:08.349Z","level":"INFO","thread":"main",
"logger":"org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter",
"message":"Detected ResponseBodyAdvice bean in responseAuditAdvice","context":"default"}
My controller method looks like this:
#PostMapping(path = "/stage", consumes = {
"application/json"
}, produces = {
"application/json"
})
#ResponseBody
public ResponseEntity<?> stage(#Valid #RequestBody StagingDto stagingDto,
#RequestHeader(HttpHeaders.USER_AGENT) String userAgent,
BindingResult bindingResult) {
I have a RequestAuditAdvice that extends RequestBodyAdviceAdapter and it is working fine. Also if the error flow occurs I see the exception advice executing as well. it is only the response advice that is failing to trigger. Any suggestions?
here is the advice bean:
#Slf4j
#RequiredArgsConstructor(onConstructor_ = #Inject)
#ControllerAdvice
public class ResponseAuditAdvice implements ResponseBodyAdvice<Object> {
private final RequestService requestService;
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
log.info("Updating audit for response.");
String ip = new String (request.getRemoteAddress().getAddress().getAddress());
requestService.auditResponse(ip, 200);
return body;
}
}
I am adding rate-limiting to a restful webservice using Spring MVC 4.1.
I created a #RateLimited annotation that I can apply to controller methods. A Spring AOP aspect intercepts calls to these methods and throws an exception if there have been too many requests:
#Aspect
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
#Autowired
private RateLimitService rateLimitService;
#Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& #annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
This all works perfectly, except when the #RateLimited controller method has parameters marked as #Valid, e.g.:
#RateLimited
#RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
#Valid #RequestBody CreateAccountRequestDto dto) {
...
}
The problem: if validation fails, the validator throws MethodArgumentNotValidException, which is handled by an #ExceptionHandler, which returns an error response to the client, never triggering my #Before and therefore bypassing the rate-limiting.
How can I intercept a web request like this in a way that takes precedence over parameter validation?
I've thought of using Spring Interceptors or plain servlet Filters, but they are mapped by simple url-patterns and I need to differentiate by GET/POST/PUT/etc.
I eventually gave up on trying to find an AOP solution and created a Spring Interceptor instead. The interceptor preHandles all requests and watches for requests whose handler is #RateLimited.
#Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
#Autowired
private final RateLimitService rateLimitService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
Add the following controller advice in your application.
#ControllerAdvice
public class ApplicationControllerAdvice {
#InitBinder
#RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
The #RateLimited should call the class RateLimitingAspect. So, after this all the constraints validator will be called.
See if it's feasible for you to implement similar logic for ##AfterThrowing advice as well which will have similar pointcut.
I'm trying to implement a binary content controller in Spring MVC.
It's working okay but I want to add caching control headers to the response.
I checked this related question: Unable to cache images served by Spring MVC
But it's using a different method. I wanted to use this requestMapping - produces annotation. Here's what I have so far, but I'm not sure how to set the response headers with the cache control elements.
#RequestMapping(value="/binaries/**", method = RequestMethod.GET, produces={MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE, MediaType.IMAGE_GIF_VALUE,
MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE})
public #ResponseBody byte[] serveResource(WebRequest webRequest, String uri) throws IOException {
String path = (String)request.getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE );
BinaryFile bf = binaryService.findByUri(path.replaceFirst("/binaries", ""));
if (webRequest.checkNotModified(bf.getLastModifiedDate().toDate().getTime()))
{
return null;
};
return bf.getResource();
}
How about this:
public #ResponseBody byte[] serveResource(WebRequest webRequest, HttpServletResponse response, String uri) throws IOException {
response.addHeader("Cache-Control", "private, max-age=0, no-cache");
// ...
You could use ResponseEntity like this:
#ResponseBody
public ResponseEntity<byte[]> serveResource() {
//..
return ResponseEntity.ok()
.lastModified(lastModified)
.body(bf);
}
Or serve resources directly with Spring Resource Handling support.
Note that multiple HTTP caching improvements are scheduled for Spring Framework 4.2, now is the time to voice your opinion on this (you can comment/votes for issues).
So I just reimplemented the resource handling and thought I'd post the answer back to this question.
Basically I used the new ResourceHandling framework, and it was surprisingly easy to implement this.
Created a BinaryImage class that's backed by a CLOB in the DB. Implement the Resource interface from Spring 4.1+.
Then I created a new ResourceResolver that looks up the incoming requests against a known path "/images".
Then configured it in the WebMVC framework.
Code for 2 & 3 follows (1 is ommitted, it's straightforward to implement the interface):
/**
* The Frontend Binary Controller
*/
#Component
public class BinaryResourceResolver implements ResourceResolver{
private static Logger logger = LogManager.getLogger(BinaryResourceResolver.class.getName());
#Autowired
HttpServletRequest request;
#Autowired
BinaryImageService binaryService;
#Override
public Resource resolveResource(HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
if (!requestPath.startsWith("/"))
{
requestPath = "/" + requestPath;
}
Resource bf = binaryService.findByUrl(requestPath);
return bf;
}
#Override
public String resolveUrlPath(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain) {
return null;
}
}
Then the configuration code: is in a config class that extends WebMVCConfigurerAdapter
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations(new String[]{"/resources/", "/products/", "/categories/"})
.setCachePeriod(30);
registry.addResourceHandler("/assets/**").addResourceLocations(new String[]{"/assets/"}).setCachePeriod(3600);
registry.addResourceHandler("/frontend/**").addResourceLocations(new String[]{"/frontend/"}).setCachePeriod(3600);
registry.addResourceHandler("/images/**").setCachePeriod(3600).resourceChain(true).addResolver(binaryResourceResolver);
registry.setOrder(-1);
}
You should provide last modified header while returning the byte array
if (webRequest.checkNotModified(bf.getLastModifiedDate().toDate().getTime()))
{
return null;
};
SimpleDateFormat dateFormat=new SimpleDateFormat();
response.addHeader("Last-Modified", dateFormat.format(bf.getLastModifiedDate().toDate().getTime()));
return bf.getResource();
My use-case:
I have multiple "kind of logical parts" in my application, that are separated by url. something like:
- someUrl/servletPath/onePartOfMyApplication/...
- someUrl/servletPath/otherPartOfMyApplication/...
Now I want to handle unmapped requests (404s) for each part differently.
How I'm handling it now:
my web.xml:
...
<error-page>
<error-code>404</error-code>
<location>/servletPath/404.html</location>
</error-page>
my controller:
#Controller
public class ExceptionController
{
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#RequestMapping(value = "/404.html")
protected String show404Page(final HttpServletRequest request)
{
final String forward = (String) request.getAttribute("javax.servlet.forward.request_uri");
// parse string and redirect to whereever, depending on context
final String redirectPath = parse(forward);
return "redirect: " + redirectPath;
}
...
My aim:
Is there a more elegant (spring-like)-way of handling 404s, instead of parsing the request in a controller or interceptor and declaring the error-page in my web.xml?
Would be nice if my controller should could look something like this:
#Controller
public class ExceptionController
{
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#RequestMapping(value = "/onePartOfMyApplication/404.html")
protected String show404PageForOnePart(final HttpServletRequest request)
{
// do something
...
return "onePartPage";
}
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#RequestMapping(value = "/otherPartOfMyApplication/404.html")
protected String show404PageForOtherPart(final HttpServletRequest request)
{
// do something different
...
return "otherPartPage";
}
I use #ExceptionHandler annotation. In controller I have something like:
private class ItemNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ItemNotFoundException(String message) {
super(message);
}
}
#ExceptionHandler
#ResponseStatus(HttpStatus.NOT_FOUND)
public void handleINFException(ItemNotFoundException ex) {
}
And then I throw an exception either in Controller (or in Service layer):
#RequestMapping("/{id}")
#ResponseBody
public Item detail(#PathVariable int id) {
Item item = itemService.findOne(id);
if (item == null) { throw new ItemNotFoundException("Item not found!"); }
return item;
}
You can do anything you like in method annotated with #ExceptionHandler. Right now in my example it shows a standard 404 error which you can customize in web.xml, but you can do much, much more. See documentation: http://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html