Spring WebFlux WebSocket path variable - spring

How can I get path variable in websocket handler with spring webflux?
I've tried this:
#Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/path/{id}", session -> session.send(Mono.just(session.textMessage("123"))));
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1);
return mapping;
}
But session doesn't have any information about url parameters.
Is it possible?

It requires some casting but it is possible.
private URI getConnectionUri(WebSocketSession session) {
ReactorNettyWebSocketSession nettySession = (ReactorNettyWebSocketSession) session;
return nettySession.getHandshakeInfo().getUri();
}
Once you have the URI use the Spring UriTemplate to get path variables.
// This can go in a static final
UriTemplate template = new UriTemplate("/todos/{todoId}");
Map<String, String> parameters = template.match(uri.getPath());
String todoId = parameters.get("todoId");

Related

Reuse existing token rather than requesting it on every request in spring boot + Retrofit app

I have a spring boot application that uses Retrofit to make requests to a secured server.
My endpoints:
public interface ServiceAPI {
#GET("/v1/isrcResource/{isrc}/summary")
Call<ResourceSummary> getResourceSummaryByIsrc(#Path("isrc") String isrc);
}
public interface TokenServiceAPI {
#FormUrlEncoded
#POST("/bbcb6b2f-8c7c-4e24-86e4-6c36fed00b78/oauth2/v2.0/token")
Call<Token> obtainToken(#Field("client_id") String clientId,
#Field("scope") String scope,
#Field("client_secret") String clientSecret,
#Field("grant_type") String grantType);
}
Configuration class:
#Bean
Retrofit tokenAPIFactory(#Value("${some.token.url}") String tokenUrl) {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(tokenUrl)
.addConverterFactory(JacksonConverterFactory.create());
return builder.build();
}
#Bean
Retrofit serviceAPIFactory(#Value("${some.service.url}") String serviceUrl, TokenServiceAPI tokenAPI) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new ServiceInterceptor(clientId, scope, clientSecret, grantType, apiKey, tokenAPI))
.build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(repertoireUrl)
.client(okHttpClient)
.addConverterFactory(JacksonConverterFactory.create());
return builder.build();
}
Interceptor to add the Authorization header to every request
public class ServiceInterceptor implements Interceptor {
public ServiceInterceptor(String clientId,
String scope,
String clientSecret,
String grantType,
String apiKey,
TokenServiceAPI tokenAPI) {
this.clientId = clientId;
this.scope = scope;
this.clientSecret = clientSecret;
this.grantType = grantType;
this.apiKey = apiKey;
this.tokenAPI = tokenAPI;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader(AUTHORIZATION_HEADER, getToken())
.addHeader(API_KEY_HEADER, this.apiKey)
.build();
return chain.proceed(newRequest);
}
private String getToken() throws IOException {
retrofit2.Response<Token> tokenResponse = repertoireTokenAPI.obtainToken(clientId, scope, clientSecret, grantType).execute();
String accessToken = "Bearer " + tokenAPI.body().getAccessToken();
return accessToken;
}
}
This is working as expected, the problem is that the token is being requested for every request rather than using the existing valid one. How can one store the token somewhere and re-use it? I was wondering if Retrofit had a built-in solution.
a possible option with caching:
add caffeiene
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
add #Cacheable("your-token-cache-name") on the method returning the token, looks like getToken above
add max cache size and expiration configuration in application.yml
e.g. 500 entries and 10 minutes for configuration below
spring.cache.cache-names=your-token-cache-name
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
example from: https://www.javadevjournal.com/spring-boot/spring-boot-with-caffeine-cache/

Spring cloud open Feign Not ignoring null Values while Encoding

I am working on a Spring boot application. We are using Spring cloud open Feign for making rest calls. We are using the default GsonEncoder(), but for some reason gson is not excluding the null properties while encoding the payload.
Config:
return Feign.builder()
.options(ApiOptions())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(ApiClient.class, "URL");
Client:
#FunctionalInterface
#FeignClient(
value = "apiTest",
url = "urlHere"
)
public interface ApiClient {
#PostMapping("path/to/service")
AiResponse getDetails(ApiRequest apiRequest);
}
ApiRequest.java:
public class ApiRequest {
private String userName;
private String userId;
private String password;
//#setters and getters
}
But while making the request, the Request Body is :
{
"userName" : "test,
"userId" : null,
"password": "password"
}
My Understanding is that Gson should automatically remove the null while serializing the Request Body. But i can see null properties exist in request.
I even tried with Custom Encoders (Jackson from below):
https://github.com/OpenFeign/feign/blob/master/jackson/src/main/java/feign/jackson/JacksonEncoder.java
As per below, it should not include null while serialzing the requestBody, but still i can see null values being passed in request.
https://github.com/OpenFeign/feign/blob/master/jackson/src/main/java/feign/jackson/JacksonEncoder.java#L39
Below are the dependencies:
Spring clou version : 2020.0.2
org.springframework.cloud:spring-cloud-starter-openfeign
io.github.openfeign:feign-gson:9.5.1
Any suggestions would be really helpful. Thanks in Advance.!
You need to provide OpenFeign with custom Encoder to let it skip nulls.
In my case this beans helps me.
#Configuration
public class ExternalApiConfiguration {
#Bean
public Feign.Builder feignBuilder() {
return Feign.builder()
.contract(new SpringMvcContract());
}
#Bean("feignObjectMapper")
public ObjectMapper feignObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
#Bean
public Encoder feignEncoder(#Qualifier("feignObjectMapper") ObjectMapper objectMapper) {
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new SpringEncoder(objectFactory);
}
And use this configuration if FeignClient
#FeignClient(configuration = ExternalApiConfiguration.class)

Streaming upload via #Bean-provided RestTemplateBuilder buffers full file

I'm building a reverse-proxy for uploading large files (multiple gigabytes), and therefore want to use a streaming model that does not buffer entire files. Large buffers would introduce latency and, more importantly, they could result in out-of-memory errors.
My client class contains
#Autowired private RestTemplate restTemplate;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
int REST_TEMPLATE_MODE = 1; // 1=streams, 2=streams, 3=buffers
return
REST_TEMPLATE_MODE == 1 ? new RestTemplate() :
REST_TEMPLATE_MODE == 2 ? (new RestTemplateBuilder()).build() :
REST_TEMPLATE_MODE == 3 ? restTemplateBuilder.build() : null;
}
and
public void upload_via_streaming(InputStream inputStream, String originalname) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
#Override public String getFilename() { return originalname; }
#Override public long contentLength() { return -1; }
};
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
body.add("myfile", inputStreamResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
String response = restTemplate.postForObject(UPLOAD_URL, requestEntity, String.class);
System.out.println("response: "+response);
}
This is working, but notice my REST_TEMPLATE_MODE value controls whether or not it meets my streaming requirement.
Question: Why does REST_TEMPLATE_MODE == 3 result in full-file buffering?
References:
How to forward large files with RestTemplate?
How to send Multipart form data with restTemplate Spring-mvc
Spring - How to stream large multipart file uploads to database without storing on local file system -- establishing the InputStream
How to autowire RestTemplate using annotations
Design notes and usage caveats, also: restTemplate does not support streaming downloads
In short, the instance of RestTemplateBuilder provided as an #Bean by Spring Boot includes an interceptor (filter) associated with actuator/metrics -- and the interceptor interface requires buffering of the request body into a simple byte[].
If you instantiate your own RestTemplateBuilder or RestTemplate from scratch, it won't include this by default.
I seem to be the only person visiting this post, but just in case it helps someone before I get around to posting a complete solution, I've found a big clue:
restTemplate.getInterceptors().forEach(item->System.out.println(item));
displays...
org.SF.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
If I clear the interceptor list via setInterceptors, it solves the problem. Furthermore, I found that any interceptor, even if it only performs a NOP, will introduce full-file buffering.
public class SimpleClientHttpRequestFactory { ...
I have explicitly set bufferRequestBody = false, but apparently this code is bypassed if interceptors are used. This would have been nice to know earlier...
#Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
public abstract class InterceptingHttpAccessor extends HttpAccessor { ...
This shows that the InterceptingClientHttpRequestFactory is used if the list of interceptors is not empty.
/**
* Overridden to expose an {#link InterceptingClientHttpRequestFactory}
* if necessary.
* #see #getInterceptors()
*/
#Override
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { ...
The interfaces make it clear that using InterceptingClientHttpRequest requires buffering body to a byte[]. There is not an option to use a streaming interface.
#Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {

Retrieving URI template used in RestTemplate call from ClientHttpRequestInterceptor

I'm using RestTemplate to call an external resource using GET where the URI is https://external.com/resource/{resourceID}.
Accordingly, my RestTemplate calls looks like the following:
restTemplate.getForObject("https://external.com/resource/{resourceID}", String.class, uriMapObject)
Where uriMapObject is a map containing the ID variable.
I have also set a ClientHttpRequestInterceptor for my RestTemplate where the interceptor's function is to create a log item for that call and send it to ElasticSearch for logging.
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException {
Map<String, Object> reqHeaders = new HashMap<>();
try{
// getPath() already contains the URI variable
String uri = httpRequest.getURI().getPath();
} catch (IllegalStateException e){
log.warn(e.getMessage());
}
.....
return response;
}
The issue is that within the ClientHttpRequestInterceptor method, I'm unable to access the original URI template used to derive the actual URL to call. I can only access the actual URL from the HttpRequest object which already has a unique identifier in it which in turn makes it impossible to aggregate all calls to https://external.com/resource/{resourceID} as a single pattern in Elastic.
Is there some way where I can get the URI template from within the interceptor?
URI template expansion is specified by the RestTemplate's UriTemplateHandler. You can customize the behavior of this by calling RestTemplate#setUriTemplateHandler, specifying your own implementation.
I would suggest creating a delegate object for a UriTemplateHandler, and wrapping what is set by default on the RestTemplate.
class LoggingUriTemplateHandler implements UriTemplateHandler {
private UriTemplateHandler delegate;
LoggingUriTemplateHandler(final UriTemplateHandler delegate) {
this.delegate = delegate;
}
public URI expand(final String template, final Map<String, ?> vars) {
//log template here
return this.delegate.expand(template, vars);
}
public URI expand(final String template, final Object... vars) {
//log template here
return this.delegate.expand(template, vars);
}
}

combining methods with same flow in controller.

Method 1.
#RequestMapping(value="/getProfessor")
public #ResponseBody List<Object> getMember(HttpServletRequest request){
HttpSession session = request.getSession();
HashMap user = (HashMap)session.getAttribute("USER_INFO");
Map<String, Object> param = new HashMap<String, Object>();
param.put("phone", (String)user.get("PHONE");
ReportManager manager = new ReportManager();
List<Object> list = manager.getProfessor(param);
}
Method 2.
#RequestMapping(value="/getMember")
public #ResponseBody List<Object> getMember(HttpServletRequest request){
HttpSession session = request.getSession();
HashMap user = (HashMap)session.getAttribute("USER_INFO");
Map<String, Object> param = new HashMap<String, Object>();
param.put("phone", (String)user.get("PHONE");
ReportManager manager = new ReportManager();
List<Object> list = manager.getMember(param);
}
The code above briefly describe how I get list of members and professors.
The two methods have exactly same code flow except URL and the bottom-most methods. As you know, one of core principles in OOP is 'combine repeating problems'. So, the point is that I want to combine these method into one method.
public Map<String, Object> getParams(HttpServletRequest request){
HttpSession session = request.getSession();
HashMap user = (HashMap)session.getAttribute("USER_INFO");
Map<String, Object> param = new HashMap<String, Object>();
param.put("phone", (String)user.get("PHONE");
}
#RequestMapping(value="/getProfessor")
public #ResponseBody List<Object> getMember(HttpServletRequest request){
ReportManager manager = new ReportManager();
List<Object> list = manager.getProfessor(this.getParams(request));
}
#RequestMapping(value="/getProfessor")
public #ResponseBody List<Object> getMember(HttpServletRequest request){
ReportManager manager = new ReportManager();
List<Object> list = manager.getProfessor(this.getParams(request));
}
Agree with you, as per DRY coding principle duplicating the same code is not recommended.
Either you can use single RequestMapping with some kind of query parameter, OR
move the common code to different method, and invoke it from two methods.
Change the url to a more generic url like /getUser.
Pass an additional TYPE param like PROFESSOR/MEMBER in the request. Depending on the TYPE you can query two different methods in your controller method.

Resources