I am using Mockito with JUnit to test an application. I need to add headers to an HttpServletRequest while mocking. This is the first time I am using mock concept to test the application. How can we set headers to the request object while using this mock concept?
Application code
#Produces({ MediaType.APPLICATION_JSON })
#Path("/devices")
public class DvrRestService {
private static final Logger logger = LoggerFactory.getLogger(DvrRestService.class);
private DvrMiddleService dvrMiddleService;
#Inject
public DvrRestService(DvrMiddleService dvrMiddleService) {
this.dvrMiddleService = dvrMiddleService;
}
#GET
#Path("/{deviceId}/metadata")
public Response getDeviceMetadata(#Context HttpServletRequest request, #PathParam("deviceId") String deviceId,
#RequiredSession final Session session) {
try {
public static String[] REQUEST_HEADERS = { "if-none-match" };
List<String> requiredHeaders = Lists.newArrayList(REQUEST_HEADERS);
Map<String, String> headers = new HashMap<String, String>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) { // here gettting NullPointerException
String headerName = headerNames.nextElement();
if (requiredHeaders.contains(headerName.toLowerCase())) {
String value = request.getHeader(headerName);
if (value != null) {
headers.put(headerName, value);
System.out.println("headerName: " + headerName + ", Value: " + value);
}
}
}
DvrResponse response = dvrMiddleService.getDeviceMetadata(deviceId.toUpperCase(), getHeaders(request));
return processResponse(response.statusCode, response.getResponse(), DeviceMetadataResponse.class,
response.getHeaders());
} catch (Exception e) {
return processErrorResponse(e, new DeviceMetadataResponse(), logger);
}
}
}
Test
public class DvrRestServiceTest {
static DvrMiddleService dms;
static HttpServletRequest request;
static Session session;
static DvrRestService drs;
public static final String DeviceId = "000004D42070";
#BeforeClass
public static void init(){
dms = mock(DvrMiddleService.class);
request = mock(HttpServletRequest.class);
session = mock(Session.class);
drs = new DvrRestService(dms);
}
#Test
public void getDeviceMetadataTest(){
Response rs = drs.getDeviceMetadata(request, DeviceId, session);
assertEquals(Response.Status.OK, rs.getStatus());
}
}
As a starting point and demonstration for the principal you can start with the following snippet.
// define the headers you want to be returned
Map<String, String> headers = new HashMap<>();
headers.put(null, "HTTP/1.1 200 OK");
headers.put("Content-Type", "text/html");
// create an Enumeration over the header keys
Enumeration<String> headerNames = Collections.enumeration(headers.keySet());
// mock HttpServletRequest
HttpServletRequest request = mock(HttpServletRequest.class);
// mock the returned value of request.getHeaderNames()
when(request.getHeaderNames()).thenReturn(headerNames);
System.out.println("demonstrate output of request.getHeaderNames()");
while (headerNames.hasMoreElements()) {
System.out.println("header name: " + headerNames.nextElement());
}
// mock the returned value of request.getHeader(String name)
doAnswer(new Answer<String>() {
#Override
public String answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return headers.get((String) args[0]);
}
}).when(request).getHeader("Content-Type");
System.out.println("demonstrate output of request.getHeader(String name)");
String headerName = "Content-Type";
System.out.printf("header name: [%s] value: [%s]%n",
headerName, request.getHeader(headerName));
}
Output
demonstrate output of request.getHeaderNames()
header name: null
header name: Content-Type
demonstrate output of request.getHeader(String name)
header name: [Content-Type] value: [text/html]
For HttpServletRequest, I would recommend using a fully functional mock type instead of directly mocking it with Mockito mocks. The spring-test library has MockHttpServletRequest for this purpose:
#BeforeClass
public static void init(){
// ...
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addHeader("Content-Type", "text/html");
mockRequest.addHeader("if-none-match", "*");
mockRequest.addHeader("customHeader", "customValue");
this.request = mockRequest;
}
Rationale
HttpServletRequest is a complex interface with over 20 methods, with well-defined interplay between them. Using a fully functional mock type for HttpServletRequest from a library simplifies the mocking, removing the need to carefully mock out the methods you're using.
One advantage of this approach is that it is more resilient in the face of future refactorings that get the same information using other methods on the class. In the case of retrieving the "if-none-match" header in HttpServletRequest, I see three different methods that could legitimately be used to retrieve the header: getHeader(String name), getHeaders(String name), and getHeaderNames(). Furthermore, the argument for both getHeader and getHeaders are case-insensitive (the same results are returned for "if-none-match", "If-None-Match", etc.), so any possible argument casing would be correct. It's very possible to support this with a direct mock, though it involves extra boilerplate code that complicates the test and makes it less obvious.
The MockHttpServletRequest class from the spring-test library mocks this interface, and allows setting the header and other values via a straightforward API. While the library is designed for testing Spring applications, the MockHttpServletRequest class is independent of any Spring-specific functionality, and should be completely usable on its own even if the application doesn't use Spring.
This worked in my case
mockMvc.perform(post("<<url>>").content("<<jsonStrig>>").header("key", "value"));
can also be used in get request.
I know the OP is using Mockito. This answer is for those using spock. You can accomplish this pretty easily.
class MyTestSpec extends Specification {
HttpServletRequest request = Mock()
MyTestClass myTestClass = new MyTestClass()
def 'my test'() {
setup:
String expectedHeader = "x-mycustom-header"
when:
String someResult = myTestClass.someTestMethod()
then:
// here is where you return your header from the mocked request
1 * request.getHeader(_ as String) >> expectedHeader
}
}
Related
I have a SpringBoot application which simply acts as a middleman. It receives an API request in JSON and forwards this to another server S by calling S's API with the exact same body.
I was exploring the solutions and came across a solution which involved the usage of RestTemplate and MultiValueMap. However, since the json body contains objects rather than simple String, I believe I have to create a DTO with corresponding POJO for the solution to work.
May I ask is the above the only solution, or there is a simple way to forward the request over and get back the response?
Even complex and nested JSON objects can be taken into a Map with key as String and value as Object.
I believe you should just use such a map as your request body and transfer the same to another api.
The middleman server can expose a endpoint that accepts a #RequestBody of Object and
HttpServletRequest then use RestTemplate to forward it to the remote server.
The middleman
#RestController
#RequestMapping("/middleman")
public class MiddleManRestController {
private RestTemplate restTemplate;
#PostConstruct
public void init() {
this.restTemplate = new RestTemplate();
this.restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(this.restTemplate.getRequestFactory()));
}
#RequestMapping(value = "/forward", method = RequestMethod.POST)
public ResponseEntity<Object> forward(#RequestBody Object object, HttpServletRequest request) throws RestClientException {
//setup the url and path
final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("Remote server URL").path("EnpointPath");
//add query params from previous request
addQueryParams(request, builder);
//specify the method
final RequestEntity.BodyBuilder requestBuilder = RequestEntity.method(HttpMethod.POST, builder.build().toUri());
//add headers from previous request
addHeaders(request, requestBuilder);
RequestEntity<Object> requestEntity = requestBuilder.body(object);
ParameterizedTypeReference<Object> returnType = new ParameterizedTypeReference<Object>() {};
//forward to the remote server
return this.restTemplate.exchange(requestEntity, returnType);
}
private void addHeaders(HttpServletRequest request, RequestEntity.BodyBuilder requestBuilder) {
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
requestBuilder.header(headerName, headerValue);
}
}
private void addQueryParams(HttpServletRequest request, UriComponentsBuilder builder) {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null) {
parameterMap.forEach((key, value) -> queryParams.addAll(key, Arrays.asList(value)));
}
builder.queryParams(queryParams);
}
}
I am using spring-boot to host a http request service.
#RequestMapping("/extract")
#SuppressWarnings("unchecked")
#ResponseBody
public ExtractionResponse extract(#RequestParam(value = "extractionInput") String input) {
// LOGGER.info("input: " + input);
JSONObject inputObject = JSON.parseObject(input);
InputInfo inputInfo = new InputInfo();
//Object object = inputObject.get(InputInfo.INPUT_INFO);
JSONObject object = (JSONObject) inputObject.get(InputInfo.INPUT_INFO);
String inputText = object.getString(InputInfo.INPUT_TEXT);
inputInfo.setInputText(inputText);
return jnService.getExtraction(inputInfo);
}
When there is a % sign, as follows, it got an errror:
http://localhost:8090/extract?extractionInput={"inputInfo":{"inputText":"5.00%"}}
The error message is below:
2018-10-09 at 19:12:53.340 [http-nio-8090-exec-1] INFO org.apache.juli.logging.DirectJDKLog [180] [log] - Character decoding failed. Parameter [extractionInput] with value [{"inputInfo":{"inputText":"5.0022:%225.00%%22}}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding. Use debug level logging to see the original, non-corrupted values.
Note: further occurrences of Parameter errors will be logged at DEBUG level.
2018-10-09 at 19:12:53.343 [http-nio-8090-exec-1] WARN org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver [140] [resolveException] - Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'extractionInput' is not present]
How to configure the URI encoding to fix this issue in my spring-boot configurations?
EDIT: Possible Java client code to make the request:
public String process(String question) {
QueryInfo queryInfo = getQueryInfo(question);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String jsonResult = null;
try {
String jsonStr = mapper.writeValueAsString(queryInfo);
String urlStr = Parameters.getQeWebserviceUrl() + URLEncoder.encode(jsonStr, "UTF-8");
URL url = new URL(urlStr);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
jsonResult = in.readLine();
in.close();
} catch (Exception jpe) {
jpe.printStackTrace();
}
return jsonResult
}
Without encoding from your client side - you could still achieve this if you follow any of the following strategies by encoding before the request is processed in the servlet:
use Spring preprocessor bean to preprocess the controller endpoint request
use Spring AspectJ to preprocess the controller endpoint request
use Spring servlet filter to preprocess the controller endpoint request
With any of the above cross-cutting strategies, you could encode the request URL and pass back to the endpoint.
For example below is one implmentation using Filter. You could possibly do some caching there if you need better performance.
#Component
public class SomeFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeFilter.class);
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletRequest modifiedRequest = new SomeHttpServletRequest(request);
filterChain.doFilter(modifiedRequest, servletResponse);
}
#Override
public void destroy() {
}
class SomeHttpServletRequest extends HttpServletRequestWrapper {
HttpServletRequest request;
SomeHttpServletRequest(final HttpServletRequest request) {
super(request);
this.request = request;
}
#Override
public String getQueryString() {
String queryString = request.getQueryString();
LOGGER.info("Original query string: " + queryString);
try {
// You need to escape all your non encoded special characters here
String specialChar = URLEncoder.encode("%", "UTF-8");
queryString = queryString.replaceAll("\\%\\%", specialChar + "%");
String decoded = URLDecoder.decode(queryString, "UTF-8");
LOGGER.info("Modified query string: " + decoded);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return queryString;
}
#Override
public String getParameter(final String name) {
String[] params = getParameterMap().get(name);
return params.length > 0 ? params[0] : null;
}
#Override
public Map<String, String[]> getParameterMap() {
String queryString = getQueryString();
return getParamsFromQueryString(queryString);
}
#Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}
#Override
public String[] getParameterValues(final String name) {
return getParameterMap().get(name);
}
private Map<String, String[]> getParamsFromQueryString(final String queryString) {
String decoded = "";
try {
decoded = URLDecoder.decode(queryString, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] params = decoded.split("&");
Map<String, List<String>> collect = Stream.of(params)
.map(x -> x.split("="))
.collect(Collectors.groupingBy(
x -> x[0],
Collectors.mapping(
x -> x.length > 1 ? x[1] : null,
Collectors.toList())));
Map<String, String[]> result = collect.entrySet().stream()
.collect(Collectors.toMap(
x -> x.getKey(),
x -> x.getValue()
.stream()
.toArray(String[]::new)));
return result;
}
}
}
You probably need to URLEncode the query parameter, e.g.
http://localhost:8090/extract?extractionInput=%7B%22inputInfo%22%3A%7B%22inputText%22%3A%225.00%25%22%7D%7D
The generally easier way to pass a parameter like this is to use an HTTP POST instead of a GET, and pass your JSON object in the body.
This is not a best practice for a REST API.
Try to normalize your URLs in object oriented way to capture path variables.
if your object likes:
param1:{
param2:{
param3: ""
}
}
use url pattern to capture attribute as:
class/param1/param2/{param3}
otherwise you will get more problems when altering front-end technologies while keeping back-end REST API same.
According to this discussion - "RESTful API - Correct behavior when spurious/not requested parameters are passed in the request", we shouldn't ignore not requested parameters but how we can process this situation on all endpoint?
For example for this endpoint:
#RequestMapping(value = "/transactions/",
method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public RestResultSupport getCommandsById(#PathVariable("id") String id) throws IOException {
validateId(id);
....
return result;
}
We'll get the same result for 2 different requests:
curl localhost:8080/?id=1200
and
curl localhost:8080/?id=1200&unknown=incorrect
If we imagine that we should process this situation on 20 endpoints, how can we simplify our code? Does Spring provide some tools for that?
I found only one way to do this - implement HandlerInterceptor.
Please have a look at an example:
public class RequestInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Set<String> innerParams = request.getParameterMap().keySet();
Set<String> describedParams = new HashSet<>();
for (MethodParameter methodParameter : ((HandlerMethod) handler).getMethodParameters()) {
if (methodParameter.hasParameterAnnotation(RequestParam.class)) {
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
describedParams.add(requestParam.name());
}
}
for (String inputRequestParam : innerParams) {
if (!describedParams.contains(inputRequestParam)) {
throw new BadDataException("Please provide valid request paramaters. [ Valid request parameters - " + describedParams + " ]");
}
}
return true;
}
... empty other required methods ...
}
Code analyzes required parameters and if it gets something unknown it will throw RuntimeException
Is there a way to get the complete path value after the requestMapping #PathVariable values have been parsed?
That is:
/{id}/{restOfTheUrl} should be able to parse /1/dir1/dir2/file.html into id=1 and restOfTheUrl=/dir1/dir2/file.html
Any ideas would be appreciated.
Non-matched part of the URL is exposed as a request attribute named HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE:
#RequestMapping("/{id}/**")
public void foo(#PathVariable("id") int id, HttpServletRequest request) {
String restOfTheUrl = new AntPathMatcher().extractPathWithinPattern(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(),request.getRequestURI());
...
}
Just found that issue corresponding to my problem. Using HandlerMapping constants I was able to wrote a small utility for that purpose:
/**
* Extract path from a controller mapping. /controllerUrl/** => return matched **
* #param request incoming request.
* #return extracted path
*/
public static String extractPathFromPattern(final HttpServletRequest request){
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String ) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
String finalPath = apm.extractPathWithinPattern(bestMatchPattern, path);
return finalPath;
}
This has been here quite a while but posting this. Might be useful for someone.
#RequestMapping( "/{id}/**" )
public void foo( #PathVariable String id, HttpServletRequest request ) {
String urlTail = new AntPathMatcher()
.extractPathWithinPattern( "/{id}/**", request.getRequestURI() );
}
Building upon Fabien Kruba's already excellent answer, I thought it would be nice if the ** portion of the URL could be given as a parameter to the controller method via an annotation, in a way which was similar to #RequestParam and #PathVariable, rather than always using a utility method which explicitly required the HttpServletRequest. So here's an example of how that might be implemented. Hopefully someone finds it useful.
Create the annotation, along with the argument resolver:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WildcardParam {
class Resolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(WildcardParam.class) != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
return request == null ? null : new AntPathMatcher().extractPathWithinPattern(
(String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE),
(String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
}
}
}
Register the method argument resolver:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new WildcardParam.Resolver());
}
}
Use the annotation in your controller handler methods to have easy access to the ** portion of the URL:
#RestController
public class SomeController {
#GetMapping("/**")
public void someHandlerMethod(#WildcardParam String wildcardParam) {
// use wildcardParam here...
}
}
You need to use built-in pathMatcher:
#RequestMapping("/{id}/**")
public void test(HttpServletRequest request, #PathVariable long id) throws Exception {
ResourceUrlProvider urlProvider = (ResourceUrlProvider) request
.getAttribute(ResourceUrlProvider.class.getCanonicalName());
String restOfUrl = urlProvider.getPathMatcher().extractPathWithinPattern(
String.valueOf(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)),
String.valueOf(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)));
I have used the Tuckey URLRewriteFilter to handle path elements that contain '/' characters, as I don't think Spring 3 MVC supports them yet.
http://www.tuckey.org/
You put this filter in to your app, and provide an XML config file. In that file you provide rewrite rules, which you can use to translate path elements containing '/' characters into request parameters that Spring MVC can deal with properly using #RequestParam.
WEB-INF/web.xml:
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<!-- map to /* -->
WEB-INF/urlrewrite.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite
PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
"http://tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
<rule>
<from>^/(.*)/(.*)$</from>
<to last="true">/$1?restOfTheUrl=$2</to>
</urlrewrite>
Controller method:
#RequestMapping("/{id}")
public void handler(#PathVariable("id") int id, #RequestParam("restOfTheUrl") String pathToFile) {
...
}
Yes the restOfTheUrl is not returning only required value but we can get the value by using UriTemplate matching.
I have solved the problem, so here the working solution for the problem:
#RequestMapping("/{id}/**")
public void foo(#PathVariable("id") int id, HttpServletRequest request) {
String restOfTheUrl = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
/*We can use UriTemplate to map the restOfTheUrl*/
UriTemplate template = new UriTemplate("/{id}/{value}");
boolean isTemplateMatched = template.matches(restOfTheUrl);
if(isTemplateMatched) {
Map<String, String> matchTemplate = new HashMap<String, String>();
matchTemplate = template.match(restOfTheUrl);
String value = matchTemplate.get("value");
/*variable `value` will contain the required detail.*/
}
}
Here is how I did it. You can see how I convert the requestedURI to a filesystem path (what this SO question is about). Bonus: and also how to respond with the file.
#RequestMapping(value = "/file/{userId}/**", method = RequestMethod.GET)
public void serveFile(#PathVariable("userId") long userId, HttpServletRequest request, HttpServletResponse response) {
assert request != null;
assert response != null;
// requestURL: http://192.168.1.3:8080/file/54/documents/tutorial.pdf
// requestURI: /file/54/documents/tutorial.pdf
// servletPath: /file/54/documents/tutorial.pdf
// logger.debug("requestURL: " + request.getRequestURL());
// logger.debug("requestURI: " + request.getRequestURI());
// logger.debug("servletPath: " + request.getServletPath());
String requestURI = request.getRequestURI();
String relativePath = requestURI.replaceFirst("^/file/", "");
Path path = Paths.get("/user_files").resolve(relativePath);
try {
InputStream is = new FileInputStream(path.toFile());
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
logger.error("Error writing file to output stream. Path: '" + path + "', requestURI: '" + requestURI + "'");
throw new RuntimeException("IOError writing file to output stream");
}
}
private final static String MAPPING = "/foo/*";
#RequestMapping(value = MAPPING, method = RequestMethod.GET)
public #ResponseBody void foo(HttpServletRequest request, HttpServletResponse response) {
final String mapping = getMapping("foo").replace("*", "");
final String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
final String restOfPath = url.replace(mapping, "");
System.out.println(restOfPath);
}
private String getMapping(String methodName) {
Method methods[] = this.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName() == methodName) {
String mapping[] = methods[i].getAnnotation(RequestMapping.class).value();
if (mapping.length > 0) {
return mapping[mapping.length - 1];
}
}
}
return null;
}
To improve upon #Daniel Jay Marcaida answer
#RequestMapping( "/{id}/**" )
public void foo( #PathVariable String id, HttpServletRequest request ) {
String restOfUrl = new AntPathMatcher()
.extractPathWithinPattern(
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(),
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString());
}
or
#RequestMapping( "/{id}/**" )
public void foo( #PathVariable String id, HttpServletRequest request ) {
String restOfUrl = new AntPathMatcher()
.extractPathWithinPattern(
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(),
request.getServletPath());
}
I have a similar problem and I resolved in this way:
#RequestMapping(value = "{siteCode}/**/{fileName}.{fileExtension}")
public HttpEntity<byte[]> getResource(#PathVariable String siteCode,
#PathVariable String fileName, #PathVariable String fileExtension,
HttpServletRequest req, HttpServletResponse response ) throws IOException {
String fullPath = req.getPathInfo();
// Calling http://localhost:8080/SiteXX/images/argentine/flag.jpg
// fullPath conentent: /SiteXX/images/argentine/flag.jpg
}
Note that req.getPathInfo() will return the complete path (with {siteCode} and {fileName}.{fileExtension}) so you will have to process conveniently.
Okay, I have a class NamedSystems, that has as its only field a Set of NamedSystem.
I have a method to find NamedSystems by certain criteria. That's not really important. When it gets results, everything works fine. However, when it can't find anything, and thus returns a null (or empty -- I've tried both ways) set, I get problems. Let me explain.
I'm using the Spring RestTemplate class and I'm making a call like this in a unit test:
ResponseEntity<?> responseEntity = template.exchange(BASE_SERVICE_URL + "?
alias={aliasValue}&aliasAuthority={aliasAssigningAuthority}",
HttpMethod.GET, makeHttpEntity("xml"), NamedSystems.class,
alias1.getAlias(), alias1.getAuthority());
Now, since this would normally return a 200, but I want to return a 204, I have an interceptor in my service that determines if a ModelAndView is a NamedSystem and if its set is null. If so, I then the set the status code to NO_CONTENT (204).
When I run my junit test, I get this error:
org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found
Setting the status to NO_CONTENT seems to wipe the content-type field (which does make sense when I think about it). So why is it even looking at it?
Spring's HttpMessageConverterExtractor extractData method:
public T extractData(ClientHttpResponse response) throws IOException {
MediaType contentType = response.getHeaders().getContentType();
if (contentType == null) {
throw new RestClientException("Cannot extract response: no Content-Type found");
}
for (HttpMessageConverter messageConverter : messageConverters) {
if (messageConverter.canRead(responseType, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + responseType.getName() + "] as \"" + contentType
+"\" using [" + messageConverter + "]");
}
return (T) messageConverter.read(this.responseType, response);
}
}
throw new RestClientException(
"Could not extract response: no suitable HttpMessageConverter found for response type [" +
this.responseType.getName() + "] and content type [" + contentType + "]");
}
Going up the chain a bit to find out where that Extractor is set, I come to RestTemplate's exchange() method that I used in the test:
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
So, it's trying to convert what amounts to nothing because of the supplied response type from the exchange call. If I change the responseType from NamedSystems.class to null, it works as expected. It doesn't try to convert anything. If I had tried to set the status code to 404, it also executes fine.
Am I misguided, or does this seem like a flaw in RestTemplate? Sure, I'm using a junit right now so I know what's going to happen, but if someone is using RestTemplate to call this and doesn't know the outcome of the service call, they would naturally have NamedSystems as a response type. However, if they tried a criteria search that came up with no elements, they'd have this nasty error.
Is there a way around this without overriding any RestTemplate stuff? Am I viewing this situation incorrectly? Please help as I'm a bit baffled.
One more way to solve this would be to make response entity as null as shown below.
ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}",
HttpMethod.DELETE,
requestEntity,
null,
userID);
If you still need response headers, try implementing the ResponseErrorHandler.
I believe you should probably look at the ResponseExtractor interface & call execute on the RestTemplate providing your implementation of the extractor. To me it looks like a common requirement to do this so have logged this:
https://jira.springsource.org/browse/SPR-8016
Here's one I prepared earlier:
private class MyResponseExtractor extends HttpMessageConverterExtractor<MyEntity> {
public MyResponseExtractor (Class<MyEntity> responseType,
List<HttpMessageConverter<?>> messageConverters) {
super(responseType, messageConverters);
}
#Override
public MyEntity extractData(ClientHttpResponse response) throws IOException {
MyEntity result;
if (response.getStatusCode() == HttpStatus.OK) {
result = super.extractData(response);
} else {
result = null;
}
return result;
}
}
I've tested this & it seems to do what I want.
To create the instance of the ResponseExtractor I call the constructor & pass the converters from a RestTemplate instance that's been injected;
E.g.
ResponseExtractor<MyEntity> responseExtractor =
new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());
Then the call is:
MyEntity responseAsEntity =
restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);
Your mileage may vary. ;-)
Here's a simple solution where you can set the default Content-Type for use if it is missing in the response. The Content-Type is added to the response header before it is handed back off to the preconfigured ResponseExtractor for extraction.
public class CustomRestTemplate extends RestTemplate {
private MediaType defaultResponseContentType;
public CustomRestTemplate() {
super();
}
public CustomRestTemplate(ClientHttpRequestFactory requestFactory) {
super(requestFactory);
}
public void setDefaultResponseContentType(String defaultResponseContentType) {
this.defaultResponseContentType = MediaType.parseMediaType(defaultResponseContentType);
}
#Override
protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
throws RestClientException {
return super.doExecute(url, method, requestCallback, new ResponseExtractor<T>() {
public T extractData(ClientHttpResponse response) throws IOException {
if (response.getHeaders().getContentType() == null && defaultResponseContentType != null) {
response.getHeaders().setContentType(defaultResponseContentType);
}
return responseExtractor.extractData(response);
}
});
}
}
This should now be fixed in Spring 3.1 RC1.
https://jira.spring.io/browse/SPR-7911
Or you could extend RestTemplate and override doExecute(..) and check the response body.
For example here is what I implemented and works for us:
#Override
protected <T> T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
throws RestClientException
{
Assert.notNull(url, "'url' must not be null");
Assert.notNull(method, "'method' must not be null");
ClientHttpResponse response = null;
try
{
final ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null)
{
requestCallback.doWithRequest(request);
}
response = request.execute();
if (!getErrorHandler().hasError(response))
{
logResponseStatus(method, url, response);
}
else
{
handleResponseError(method, url, response);
}
if ((response.getBody() == null) || (responseExtractor == null))
{
return null;
}
return responseExtractor.extractData(response);
}
catch (final IOException ex)
{
throw new ResourceAccessException("I/O error: " + ex.getMessage(), ex);
}
finally
{
if (response != null)
{
response.close();
}
}
}
I think you are right.
I'm having a similar problem.
I think we should be getting a ResponseEntity with a HttpStatus of NO_CONTENT and a null body.
I came along a workaround (not sure if it meets your case):
First define a custom interceptor class which implements ClientHttpRequestInterceptor. and check if response.getStatusCode() meets your case (my case is != HttpStatus.NOT_FOUND and response.getBody() length is 0), define a custom class (e.x. DefaultResponseForEmptyRestTemplateBody) which has a static method of type MockClientHttpResponse:
public class RequestResponseInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
if(response.getStatusCode()!=HttpStatus.NOT_FOUND && response.getBody().readAllBytes().length==0){
response = DefaultResponseForEmptyRestTemplateBody.getResponse(response.getStatusCode());
}
return response;
}
}
public static class DefaultResponseForEmptyRestTemplateBody {
MockClientHttpResponse response;
private static byte[] content = new byte[0];
public static MockClientHttpResponse getResponse(HttpStatus statusCode){
content = "response body is empty".getBytes();
return new MockClientHttpResponse(content, statusCode);
}
}
finally add this interceptor to your restTemplate object as below:
restTemplate.setInterceptors(Collections.singletonList(new RequestResponseLoggingInterceptor()));
and call your restTemplate.postForEntity:
ResponseEntity<String> response = this.restTemplate.postForEntity(baseUrl, requestParams,String.class);