After creating a resource in Spring REST Controller , I am returning it's location in header as below.
#RequestMapping(..., method = RequestMethod.POST)
public ResponseEntity<Void> createResource(..., UriComponentsBuilder ucb) {
...
URI locationUri = ucb.path("/the/resources/")
.path(someId)
.build()
.toUri();
return ResponseEntity.created(locationUri).build();
}
In Unit Test, I am checking its location as below.
#Test
public void testCreateResource(...) {
...
MockHttpServletRequestBuilder request = post("...")
.content(...)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON);
request.session(sessionMocked);
mvc.perform(request)
.andExpect(status().isCreated())
.andExpect(header().string("Location", "/the/resources" + id);
}
This result cases fails with following message.
java.lang.AssertionError: Response header Location expected:</the/resources/123456> but was:<http://localhost/the/resources/123456>
Seems like I have to provide context prefix http://localhost for Location header in expectation.
Is it safe to hard code context? If so, why?
If not, what is right way to generate it correctly for test case?
I'm guessing because you are using UriComponentsBuilder to build your URI it's setting the host name in your location header. Had you used something like just new URI("/the/resources"), your test would have passed.
In your case, I'd use redirectedUrlPattern to match the redirection URL:
.andExpect(redirectedUrlPattern("http://*/the/resources"))
This will match any hostname, so you don't have to hardcode localhost. Learn more about different patterns that you can use with AntPathMatcherhere.
If you don't need to have a full URI in the Location header on the response (i.e. no requirement, design constraint etc...): Consider switching to use a relative URI ( which is valid from HTTP standards perspective - see [1]: https://www.rfc-editor.org/rfc/rfc7231 ) Relative URIs is a proposed standard that is supported by modern browsers and libraries. This will allow you to test the behavior of the endpoint and make it less fragile in the long run.
If you need to assert the full path, since you are using MockMvc, you can set the uri in the test request to exactly what you want:
#Autowired
private WebApplicationContext webApplicationContext;
#Test
public void testCreateResource() {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mvc.perform(MockMvcRequestBuilders.get(new URI("http://testserver/the/resources")));
This will make the injected builder produce "http://testserver" when build is called. Note of caution, a framework change in the future could cause you headaches if they remove this test behavior.
Facing the same problem, I tried out the solution Suraj Bajaj provided, and it worked fine for me.
I then took a try on asserting the text of the header field "Location" directly and ended up using a containsString().
This simple solution should also be a feasible alternative, as long as server context and the question of relative/absolute path are not of interest:
mvc.perform(request)
.andExpect(status().isCreated())
.andExpect(header().string("Location", containsString("/the/resources"+id)));
Related
Problem
I have a simple Spring Boot app with a basic RestController (full code available here). It consumes JSON and uses Jackson to convert request from JSON and response to JSON.
#RestController("/")
#RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
#Autowired
private SomeService someService;
#PostMapping
public ResponseEntity<SomeResponseDto> post(#RequestBody #Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
After start-up, the 1st request is about 10-times slower than any sub-sequent request. I debugged and profiled the app and it seems that on first request a Jackson JSON parser is getting initialized somewhere in AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters and AbstractJackson2HttpMessageConverter.
In sub-sequent requests, it seems to get re-used.
Question
How do I initialize Jackson JSON parsing during start-up so that also 1st request is fast?
I know how to trigger a method after Spring started. In PreloadComponent I added as an example how to do a REST request against the controller.
#Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
#Autowired
private Environment environment;
#Autowired
private WebClient.Builder webClientBuilder;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" + serverPort;
final String warmUpEndpoint = baseUrl + "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " + response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
This only works in this toy example. In reality, I have many REST endpoints with complex DTOs and I would need to add a "warm-up" endpoint next to each "real" endpoint as I can't call my real endpoints.
What I already tried?
I added a second endpoint with a different DTO and called it in my PreloadComponent. This doesn't solve the problem. I assume that an Jackson / whatever instance is created for each type.
I autowired ObjectMapper into my PreloadComponent and parsed JSON to my DTO. Again, this doesn't solve the issue.
Full source available at: https://github.com/steinsag/warm-me-up
It turns out that Jackson validation is the problem. I added the JVM option
-verbose:class
to see when classes get loaded. I noticed that on 1st request, there are many Jackson validation classes getting loaded.
To confirm my assumption, I re-worked my example and added another independent warm-up controller with a distinct DTO.
This DTO uses all Java validation annotations also present like in the real DTO, e.g. #NotNull, #Min, etc. In addition, it also has a custom enum to also have validation of sub-types.
During start-up, I now do a REST request to this warm-up endpoint, which doesn't need to contain any business logic.
After start-up, my 1st request is now only 2-3 times slower than any sub-sequent requests. This is is acceptable. Before, the 1st request was 20-40 times slower.
I also evaluated if really a REST request is needed or if it is sufficient to just do JSON parsing or validation of a DTO (see PreloadComponent). This reduces runtime of 1st request a bit, but it is still 5-15 times slower than with proper warm-up. So I guess a REST request is needed to also load other classes in Spring Dispatcher, etc.
I updated my example at: https://github.com/steinsag/warm-me-up
I believe, that a lot of classes will be lazy-loaded. If first call performance is important, then I think warming up by calling each endpoint is the way to go.
Why do you say, that you cannot call the endpoints? If you have a database and you don't want to change the data, wrap everything in a transaction and roll it back after the warm up calls.
I haven't seen any other method to solve this, which doesn't necessarily mean, that it doesn't exist ;)
Using spring-web, I am mapping a method to receive a request containing dots "." on the path:
#RequestMapping(value = "download/{id:.+}", method = RequestMethod.GET, produces = "application/xls")
public String download(#PathVariable(value = "id") String id) { ... }
For example, /download/file.xls should be a valid address. But when I try to access that address, Spring returns Could not find acceptable representation as if it was trying to find a resource named file.xls.
Spring shouldn't execute download method rather than try to find a resource named as the path variable?
Obs.: my application is a spring-boot application.
Your #RequestMapping says it produces "application/xls", but your return type is a String and you haven't annotated the return type with #ResponseBody.
If you want to return an Excel spreadsheet, you need to produce that spreadsheet on the server and return it as a byte[] from your request mapping. I'm not sure how or why you'd return a String, unless you're controller is a simple #Controller and you're returning the view name.
Have you tried configuring your RequestMappingHandlerMapping
handler.setUseSuffixPatternMatch( false )
(I was configuring my RequestMappingHandlerMapping anyway, so for me I just needed to add that line - chances are you may be letting Spring Boot autoconfig that class).
See https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.html#setUseRegisteredSuffixPatternMatch-boolean-
Possibly you may need to turn off content negotiation as well - I can't remember exactly what Spring Boot default content negotiation is, but it might be affecting your case.
#Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
}
Worth noting that if you are working on a wider/existing application then both these configurations have possible implications more widely, so if that is the case then tread carefully!
I have a legacy system and some url rewrite rule that we want to get rid. One of which is a rule to change is /tools/lookup.html?what=this and change it to /tools/search?what=this and actually returns json and not html !
I'm trying to find a way to have a #Controller to support the legacy format lookup.html, but it fails with HTTP 406 "The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.". I'm wondering if anyone as done something similar?
My controller methods looks like:
#RequestMapping(value = "/tools/lookup.html", method = RequestMethod.GET)
public #ResponseBody Result lookup() {
return result;
}
Thanks in advance
Sylvain
Removing the reponsebody annotation will stop the controlle method returning json.
Take a closer look at #RequestMapping, which supports a produces element. E.g.,
#RequestMapping(value = "/tools/search", produces = "application/json")
#ResponseBody
public Result search(...) { ... }
The issue comes from how spring is treating pathvariables. The default behavior will cut off the last dot in a url (.html) to find the request mappings.
This effect happens only for the last pathvariable.
I havent found a property yet to change this globally but one way is to tell your pathvariable mapping to use the regex {pathvariable:.+}.
#Requestmapping("/somepath/{varwithextention:.+}")
public String method(#Pathvariable String varwithextension) {
...
}
Edit: i see that you do not even use pathvars. Probably its still the same effect for the last url part though?
Hi finally found something that should work in my case, note that my app doesn't need to support real html (REST only app), so this shouldn't have to much side effects. In my WebMvcConfigurerAdapter I added the following media type for html.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("html",MediaType.APPLICATION_JSON);
configurer.mediaType("html",MediaType.APPLICATION_XML);
super.configureContentNegotiation(configurer);
}
I now I get my JSON or XML content back in the client. No more 406 error.
I'm trying to use #RequestMapping with the consumes-element. Reading the API-document it works on the Content-Type-header of the request. However, using
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-8", value = "/test")
public void test() {
:
}
or
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=ISO-8859-1", value = "/test")
public void test() {
:
}
doesn't make a difference. The header in the request can look like
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
or
Content-Type: application/x-www-form-urlencoded
test() will be called in all four possible constellations.
However, and this is proof to me that Spring sees and tries to use the charset-part, if I specify
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-x8", value = "/test")
public void test() {
:
}
I get an exception during startup (!) of the web-app:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Initialization of bean failed;
nested exception is java.nio.charset.UnsupportedCharsetException: UTF-x8
Note that the documentation on the produces-element also doesn't mention the use of charset, but according to Google some use it.
Any clues to what's happening here or what I'm doing wrong?
BTW, this is Spring 3.1.1.RELEASE.
I think you have already answered your question, so this is more of a confirmation, from a code point of view, as to why the charset is not taken into account when resolving mappings.
When digging into Spring code, the culprit seems to be the MediaType#includes(). method
More digging reveals that a RequestMappingInfo is created in association to the RequestMapping annotation of the method. This RequestMappingInfo stores a series of AbstractRequestCondition objects, one of them being the ConsumesRequestCondition which holds the MediaType defined in the consumes part of the annotation (i.e. application/x-www-form-urlencoded;charset=UTF-8).
Later when a request is made, this ConsumesRequestCondition has an inner ConsumeMediaTypeExpression class with a matchMediaType() method that extracts the MediaType of the HttpServletRequest and checks it against it's own MediaType to see if it's included.
If you look at the MediaType#includes() implementation (Lines 426 to 428), it returns true when type (i.e. application) and subtype (i.e. x-www-form-urlencoded) are equal, completely disregarding the parameters Map which in this case
holds the remnant "charset","UTF-8" combination.
Digging into the produces track seems to show similar results, but in this case it's the MediaType#isCompatibleWith() method involved, and again, it reaches only to type and subtype if they are equal.
If you found evidence on Google of the produces working for charset request mapping, I would doubt it (unless they changed core Spring stuff)
As to why it was designed this way, well that's another question :)
I am building a webservices application using jersey,jax-rs
I have single jax-rs resource file at path "/authenticate"
I have multiple methods with individual paths like "/user" "/test"
#Path ("/authenticate")
public class Authenticate{
private static final Log log = LogFactory.getLog(Authenticate.class);
#QueryParam("entityId")
String entity;
#GET
#Path ("/{param}")
public Response getMsg(#PathParam ("param") String msg) {
String o = "Hello Welcome Back:"+msg;
return Response.status(200).entity(o).build();
}
#GET
#Path ("/user")
#Produces({"application/json"})
public UserDTO getUser (#Context HttpServletRequest request,
#QueryParam("userId") int userId) {
System.out.println("In Get User, User:"+userId);
System.out.println("In Get User, Entity:"+entity);
}
#GET
#Path ("/test")
#Produces({"application/json"})
public TestPOJO getTestPOJO () {
System.out.println("In Get TestPOJO");
System.out.println("In Get Test, Entity:"+entity);
return new TestPOJO();
}
}
As suggested for jersey client, I am using a single webresource from client and build subsequent webresources from the same webresource by using .path("/xxx").
Here is how I create the initial web resource
WebResource webResource = client.resource("http://localhost:8080/Service/jaxrs/authenticate");
webResource.queryParam("entityId", securityHelper.getEntityId().toString());
Here is how I use the webresource subsequently
MultivaluedMap<String, String> params = new MultivaluedMapImpl();
ClientResponse userRes = webResource.path("/user").queryParams(params).accept("application/json").get(ClientResponse.class);
I want to assign a queryparam to the initial webresource, and I want that to be retained by all subsequent webresources created using the .path(). But that is not happening right now. For example in the above code "entityId" is not available when the call with path("/user") is made.
My idea is to assign common parameters once and all subsequent users of the webResource need not add those again and again. Is there a way to do it? Will this approach work?
The line below creates a new WebResource and not changing the state of the webResource object:
webResource.queryParam("entityId", securityHelper.getEntityId().toString())
Eventually you could change your code like this to create the "base" resource:
WebResource webResource = client.resource("http://localhost:8080/Service/jaxrs/authenticate").queryParam("entityId", securityHelper.getEntityId().toString());
And then use this resource to create another resources as you like. WebResource.queryParam and WebResource.queryParams always create a new WebResource object.
I'm may not be the best person to answer this since I have entered the "world" of Jersey and RESTful servers not too long ago but since i saw this unanswered for 2 days ill try to help out as best as I can.
If i understood correctly you are trying to, by using a query, save the user information on entityId String so it will be available when you make a subsequent call.
Ok let's start with what you have. With your code (entityId as a global parameter), what you are specifying , is that when you are calling a resource from the Authenticate class, any call can be made with a query of the type '?entityId="something" and ANY method in this class can use the information sent in the query.
The thing is, for what I've learned by messing about with Jersey, whenever you make a call, the resource class (in your case Authenticate) is instantiated again. Therefor you can't just keep information in a global parameter since subsequent calls will have the String entityId as null.
This means that if you want to save the information you'll have to do it in a external resource (ex: db, file, etc). What method you choose depends on what you want to do and what are you looking for in your application.
I hope I was at least able to shed a sliver of light on your problem.