Question in short: How can I pass a dynamic value to Spring REST interceptor after the RestTemplate is Autowired?
Now a detailed explanation:
I have a Spring REST interceptor as below:
public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor {
private final String headerName;
private final String headerValue;
public HeaderRequestInterceptor(final String headerName, final String headerValue) {
this.headerName = headerName;
this.headerValue = headerValue;
}
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
HttpRequest wrapper = new HttpRequestWrapper(request);
wrapper.getHeaders().set(headerName, headerValue);
return execution.execute(wrapper, body);
}
}
And then I configure my RestTemplate as below with the above interceptor:
#Bean
public RestTemplate getRestTemplate() {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new HeaderRequestInterceptor(<<MY-CUSTOM-HEADER>>, <<MY-DYNAMIC-VALUE>>));
final RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
Please look at the way I am creating the interceptor. MY-CUSTOM-HEADER is a constant, but MY-DYNAMIC-VALUE can change for every request. How can I make the interceptor take dynamic value?
PS: This is a standalone spring application (not web). It's a kind of client library that will be used to make REST calls.
I don't see the usage of interceptor if your headers are dynamic per request.
Request based scope bean on RestTemplate can solve the issue but it creates RestTemplate object per request.
Simply adding a static utility class called HttpRequestHeaderUtils.java with addHeader() method and call it before calling RestTemplate methods or wrap it on your own CustomRestTemplate to delegate all calls of RestTemplate to have single implementation.
CustomRestTemplate extends RestTemplate{
}
HttpRequestHeaderUtils.java
public static void addHeader(final HttpRequest request, final String headerName, final String headerValue)
HttpRequest wrapper = new HttpRequestWrapper(request);
wrapper.getHeaders().set(headerName, headerValue);
}
If you still want to use interceptor, you can hijack your intercept method
by storing the dynamic value as a request attribute (which is not clean, its same as setting header directly instead )
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
HttpRequest wrapper = new HttpRequestWrapper(request);
final String dynamicHeaderValue = request.getAttribute("myDynamicHeaderValue");
wrapper.getHeaders().set(headerName, dynamicHeaderValue!=null? dynamicHeaderValue : headerValue );
return execution.execute(wrapper, body);
}
If the header value can change in every request, the RestTemplate bean should have the request scope
#Bean(scope = DefaultScopes.REQUEST)
#ScopedProxy
public RestTemplate getRestTemplate(HttpRequest request) {
// ...
interceptors.add(new HeaderRequestInterceptor(
"custom-header"
, myDynamicVal));
//
}
I used a scoped proxy because I assumed the enclosing class is injected in the default (singleton) scope. Also note that I added the HttpRequest as a dependency in the getRestTemplate() method assuming that you need request data to fill the header - if you don't simply remove it and add whatever you like.
Related
Context
I am currently working on a JavaEE project with a lot of existing resource based JAX-RS services. For this project we would like to have batch processing to prevent a lot of separate calls and, most importantly, to execute these different methods in a transactional context for rollback purposes with the native MongoDB driver. We want to avoid manually creating new methods for all possible combinations. I could not find any solution to this issue on Stack Overflow so I started analyzing the implementation of RESTEasy and I came up with the following solution.
Below a simplified/pseudo version of my code:
JAX-RS method
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Path("execute")
public Response executeBatch(BatchRequestWrapper batchRequestWrapper) throws UnsupportedEncodingException
{
// Retrieve information from context
HttpServletRequest httpServletRequest = ResteasyProviderFactory.getContextData(HttpServletRequest.class);
HttpServletResponse httpServletResponse = ResteasyProviderFactory.getContextData(HttpServletResponse.class);
ServletContext servletContext = ResteasyProviderFactory.getContextData(ServletContext.class);
HttpResponse httpResponse = ResteasyProviderFactory.getContextData(HttpResponse.class);
SynchronousDispatcher dispatcher = (SynchronousDispatcher) ResteasyProviderFactory.getContextData(Dispatcher.class);
ResteasyHttpHeaders httpHeaders = (ResteasyHttpHeaders) ResteasyProviderFactory.getContextData(HttpHeaders.class);
ResteasyUriInfo uriInfo = (ResteasyUriInfo) ResteasyProviderFactory.getContextData(UriInfo.class);
// Create Mongo Client Session object and save it in a Singleton which contains a ThreadLocal object so that DAO layer can reuse the client session object for all methods.
// Iterate over all the methods and invoke dispatcher
for (BatchRequest batchRequest : batchRequestWrapper.getBatchRequests())
{
// Update URI based on specific endpoint
uriInfo.setRequestUri(URI.create(batchRequest.getUri()));
// Temporary use mock response for the response
MockHttpResponse response = new MockHttpResponse();
// Create httpservletinput message from RESTEasy lib to pass to the dispatcher. It will automatically resolve all parameters/methods etc.
HttpServletInputMessage request = new HttpServletInputMessage(httpServletRequest, httpServletResponse, servletContext, httpResponse, httpHeaders, uriInfo, batchRequest.getHttpMethod(), dispatcher);
// Set body in input stream if body is specified. This will inject the correct 'body' parameters in the methods. Query and Path parameters are already resolved in the method above.
if(!Strings.isNullOrEmpty(batchRequest.getBody()))
{
InputStream targetStream = new ByteArrayInputStream(batchRequest.getBody().getBytes(StandardCharsets.UTF_8));
request.setInputStream(targetStream);
}
// Actual invoke
dispatcher.invoke(request, response);
// Do something with response object
}
// Clean or abort session based on invoke result
return Response.ok().entity(null).build();
}
Request Object
public class BatchRequestWrapper
{
private List<BatchRequest> batchRequests;
public List<BatchRequest> getBatchRequests()
{
return batchRequests;
}
public void setBatchRequests(List<BatchRequest> batchRequests)
{
this.batchRequests = batchRequests;
}
}
public class BatchRequest
{
private String uri;
private String httpMethod;
private String body;
public String getUri()
{
return uri;
}
public void setUri(String uri)
{
this.uri = uri;
}
public String getHttpMethod()
{
return httpMethod;
}
public void setHttpMethod(String httpMethod)
{
this.httpMethod = httpMethod;
}
public String getBody()
{
return body;
}
public void setBody(String body)
{
this.body = body;
}
}
My solution works with one new REST method and let's me reuse all the existing JAX-RS annotated methods in the project. Before I actually fully implement this and bring it to production, I would like to know if this is the way to actually do this or are there better alternatives? I am not a big fan of the hard dependency on RESTEasy though.
I added an async endpoint to a existing spring-mvc application:
#RestController
public class MyController {
#PostMapping("/")
public Mono<String> post(Object body) {
return Mono.just("test");
//webClient.retrieve().bodyToMono(String.class);
}
}
I want to create a global interceptor/filter that will log the request body payload. But how can I get access to it?
I tried adding a HandlerInterceptorAdapter, but the payload is always empty:
static class LoggingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
byte[] buf = wrapper.getContentAsByteArray();
System.out.println(buf);
System.out.println(buf.length);
return true;
}
}
Maybe the payload is not yet present in the request, or has already been read. So how can I access the body in this async case?
Unfortunately in Webflux you cannot use HandlerInterceptorAdapter because it came from web mvc module and works only with the servlets.
I found a good article with solutions.
P.S. You must to remove spring-mvc dependencies if going to use reactive endpoins.
Depends on request body content I need to redirect http requests to URL_1 or URL_2.
I started controller implementation:
#RestController
public class RouteController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public HttpServletResponse route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
//send request to URL_1 and get response
} else {
//send request to URL_2 and get response
}
}
}
Request might be GET or POST ot PUT or PATCH etc.
Could you help me to write that code?
I've asked a somehow similar question a while ago. Plea see Server side redirect for REST call for more context.
The best way (to my current understanding) you could achieve this is by manually invoking the desired endpoints from your initial endpoint.
#RestController
public class RouteController {
#Value("${firstUrl}")
private String firstUrl;
#Value("${secondUrl}")
private String secondUrl;
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public void route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
restTemplate.exchange(firstUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params));
} else {
restTemplate.exchange(secondUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params))
}
}
}
Example implementation for getHttpMethod:
public HttpMethod getHttpMethod(HttpServletRequest request) {
return HttpMethod.valueOf(request.getMethod());
}
Similar implementations for getHttpEntity, getResponseClass and getParams. They are used for converting the data from the HttpServletRequest request to the types required by the exchange method.
There seem to be a lot of better ways of doing this for a Spring MVC app, but I guess that it does not apply to your context.
Another way you could achieve this would be defining your own REST client and adding the routing logic there.
My requested path is:
localhost:8080/companies/12/accounts/35
My Rest Controller contains this function and I want to get companyId and accountId inside Filter.
#RequestMapping(value = "/companies/{companyId}/accounts/{accountId}", method = RequestMethod.PUT)
public Response editCompanyAccount(#PathVariable("companyId") long companyId, #PathVariable("accountId") long accountId,
#RequestBody #Validated CompanyAccountDto companyAccountDto,
HttpServletRequest req) throws ErrorException, InvalidKeySpecException, NoSuchAlgorithmException
Is there any function that can be used in order to receive this information inside filter?
Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
String companyId = (String)pathVariables.get("companyId");
String accountId= (String)pathVariables.get("accountId");
If you are referring to the Spring web filter chain, you will have to manually parse the URL provided in the servlet request. This is due to the fact that filters are executed before the actual controller gets hold of the request, which then performs the mapping.
It's more suitable to do that inside a Spring Filter(an interceptor).To store the retrieved value in order to use it later in the controller or service part, consider using a Spring bean with the Scope request(request scope creates a bean instance for a single HTTP request).
Below the interceptor code example:
#Component
public class RequestInterceptor implements Filter {
private final RequestData requestData;
public RequestInterceptor(RequestInfo requestInfo) {
this.requestInfo = requestInfo;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//Get authorization
var authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
//Get some path variables
var pathVariables = request.getHttpServletMapping().getMatchValue();
var partyId = pathVariables.substring(0, pathVariables.indexOf('/'));
//Store in the scoped bean
requestInfo.setPartyId(partyId);
filterChain.doFilter(servletRequest, servletResponse);
}
}
For safe access of the storing value in the RequestData bean, I advise to always use a ThreadLocal construct to hold the managed value:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData {
private final ThreadLocal<String> partyId = new ThreadLocal<>();
public String getPartyId() {
return partyId.get();
}
public void setPartyId(String partyId) {
this.partyId.set(partyId);
}
}
by adding Interceptor it would work. complete code to the problem : https://stackoverflow.com/a/65503332/2131816
Is there a way to get the http request (header content) in a REST method? I'm using spring framework.
I want to build a new request from the current request to a different server in the REST method. This is more like a proxy/forwarding service - so I want to preserve the stuff I want in the original request.
I do not have much options -otherwise I would not have used REST for such stuff.
Does spring framework provide such interface?
For example - if I want to get hold of the request headers in the greeting method in the code below ( example code from spring.io )
#Controller
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public #ResponseBody Greeting greeting(
#RequestParam(value="name", required=false, defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
You can simply give your handler method a parameter of type HttpServletRequest and Spring will provide it for you.
#RequestMapping("/greeting")
public #ResponseBody Greeting greeting(
#RequestParam(value="name", required=false, defaultValue="World") String name,
HttpServletRequest request) {
HttpServletRequest provides a number of methods to retrieve HTTP headers.
Alternatively, Spring also provides the #RequestHeader annotation that can be used like #RequestParam to retrieve a header from the HTTP request.