set content range in spring - spring

I have many resting api in my spring boot application.
#GetMapping("lc/all")
List<LC> getAll()
{
return lcRepository.findAll();
}
Mainly they are sending list.
Now for some reason, I have to receive the length of the response. Changing for each and every method would be tedious.
How can I set Content-Range for each method automatically.
I have modified the CORS:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins("http://localhost:5000")
.exposedHeaders("Content-Range");
}
};
}

First you need a filter that writes only the requested range part as a response when range header is present.
package com.example.contentrange;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE;
import java.io.IOException;
import java.util.Arrays;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpRange;
import org.springframework.web.util.ContentCachingResponseWrapper;
#WebFilter("/*")
public class AddResponseHeaderFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
var rangeHeader = httpServletRequest.getHeader("Range");
// if there is no range request in the header than do the "normal" filtering
if (rangeHeader == null) {
chain.doFilter(request, response);
return;
}
HttpRange range = HttpRange.parseRanges(rangeHeader).get(0);
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper((HttpServletResponse) response);
try {
chain.doFilter(request, responseWrapper);
} finally {
byte[] copy = responseWrapper.getContentAsByteArray();
int size = responseWrapper.getContentSize();
int lower = (int) range.getRangeStart(size);
int upper = (int) range.getRangeEnd(size);
if (lower <= size) {
responseWrapper.setStatus(SC_PARTIAL_CONTENT);
byte[] subArray = Arrays.copyOfRange(copy, lower, upper + 1);
String newContent = new String(subArray, UTF_8);
responseWrapper.reset();
responseWrapper.setHeader(
"Content-Range", String.format("bytes %d-%d/%d", lower, upper, size));
responseWrapper.setContentLength(newContent.length());
responseWrapper.getWriter().write(newContent);
responseWrapper.getWriter().flush();
responseWrapper.flushBuffer();
responseWrapper.copyBodyToResponse();
} else {
responseWrapper.setStatus(SC_REQUESTED_RANGE_NOT_SATISFIABLE);
}
}
}
}
Second you need to add org.springframework.boot.web.servlet.ServletComponentScan annotation to your application class.
#ServletComponentScan
#SpringBootApplication
public class ContentRangeApplication {
public static void main(String[] args) {
SpringApplication.run(ContentRangeApplication.class, args);
}
}
For more info on Range: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests

Related

How to prevent Redis writes for anonymous user sessions

I have this sample application:
package com.example.session;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class DemoRedisDataSessionApplication {
#Configuration
#EnableWebSecurity
#EnableRedisHttpSession(redisNamespace = "demo-redis-data-session")
public static class AppConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("0000").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and()
.authorizeRequests().antMatchers("/ping").permitAll().and()
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
#RestController
public static class AppController {
#GetMapping("/ping")
public String ping() {
return "pong";
}
#GetMapping("/secured")
public String secured() {
return "secured";
}
}
public static void main(String[] args) {
SpringApplication.run(DemoRedisDataSessionApplication.class, args);
}
}
When I hit /secured I get 302 redirected to the /login form, which is what I expect if I am not logged in, but I get some unwanted entries in Redis:
127.0.0.1:6379> keys *
1) "spring:session:demo-redis-data-session:sessions:expires:dbb124b9-c37d-454c-8d67-409f28cb88a6"
2) "spring:session:demo-redis-data-session:expirations:1515426060000"
3) "spring:session:demo-redis-data-session:sessions:dbb124b9-c37d-454c-8d67-409f28cb88a6"
I don't want to create this data for every anonymous user (read crawler), so is there a way to prevent these Redis entries when hitting a secured endpoint/page with an anonymous user?
Additional data used for this sample project
 docker-compose.yml
version: "2"
services:
redis:
image: redis
ports:
- "6379:6379"
Spring Boot version
1.5.9.RELEASE
This is not the optimal solution since it creates only one session for all crawlers, but at least I don't get Redis full of unwanted session.
import lombok.extern.log4j.Log4j;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Log4j
#Component
public class CrawlerManagerSessionStrategyWrapper implements MultiHttpSessionStrategy {
private CookieHttpSessionStrategy delegate;
private volatile String crawlerSessionId;
public CrawlerManagerSessionStrategyWrapper() {
this.delegate = new CookieHttpSessionStrategy();
}
public String getRequestedSessionId(HttpServletRequest request) {
String sessionId = getSessionIdForCrawler(request);
if (sessionId != null)
return sessionId;
else {
return delegate.getRequestedSessionId(request);
}
}
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
delegate.onNewSession(session, request, response);
if (isCrawler(request)) {
crawlerSessionId = session.getId();
}
}
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
delegate.onInvalidateSession(request, response);
}
public HttpServletRequest wrapRequest(HttpServletRequest request, HttpServletResponse response) {
return request;
}
public HttpServletResponse wrapResponse(HttpServletRequest request, HttpServletResponse response) {
return response;
}
private String getSessionIdForCrawler(HttpServletRequest request) {
if (isCrawler(request)) {
SessionRepository<Session> repo = (SessionRepository<Session>) request.getAttribute(SessionRepository.class.getName());
if (crawlerSessionId != null && repo != null) {
Session session = repo.getSession(crawlerSessionId);
if (session != null) {
return crawlerSessionId;
}
}
}
return null;
}
private boolean isCrawler(HttpServletRequest request) {
// Here goes the logic to understand if the request comes from a crawler, for example by checking the user agent.
return true;
}
}
The only thing to implement is the isCrawler method to state if the request comes from a crawler.

spring boot RestController get HttpServletResponse content

I use spring boot build project, RestController return string data.
I want get response content in Filter.
But cant get, please help me.
controller:
#RestController
#RequestMapping(value = "/service/example")
public class ExampleController {
#RequestMapping(value = "/get/test", method = RequestMethod.POST)
public String message(#RequestBody String data) {
return "test";
}
#RequestMapping(value = "/get/test1", method = RequestMethod.POST)
public void message(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.write("dfsfd");
writer.flush();
}
}
filter:
#WebFilter(filterName="myFilter",urlPatterns="/service/*")
public class MyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
MyHttpServletResponseWrapper responseWrapper = new MyHttpServletResponseWrapper(response);
filterChain.doFilter(request, responseWrapper);
String responseContent = responseWrapper.getContent();
System.out.println("response="+responseContent);
}
}
MyHttpServletResponseWrapper :
public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter cachedWriter;
private CharArrayWriter bufferedWriter;
/**
* Constructs a response adaptor wrapping the given response.
*
* #param response The response to be wrapped
* #throws IllegalArgumentException if the response is null
*/
public MyHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
bufferedWriter = new CharArrayWriter();
cachedWriter = new PrintWriter(bufferedWriter);
}
#Override
public PrintWriter getWriter() throws IOException {
return cachedWriter;
}
/**
* 获取原始HTML
*
* #return
*/
public String getContent() {
byte[] bytes = bufferedWriter.toString().getBytes();
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
}
post to /service/example/get/test cant get content.
but post to /service/example/get/test1 can get content.
why?
My project has many rest like /service/example/get/test, I dont want to change each one.
how to get response content in filter, please help, Thanks!!!
I created one simple spring boot project, in this project you can control which url you want to filter:
Rest service class (3 services, we will filter 2 only)
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.GenericFilterBean;
#SpringBootApplication
#RestController
#RequestMapping(value = "/service/example")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#RequestMapping(value = "/get/test", method = RequestMethod.POST)
public String message(#RequestBody String data) {
return "test";
}
#RequestMapping(value = "/get/test1", method = RequestMethod.POST)
public void message(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.write("dfsfd");
writer.flush();
}
#RequestMapping(value = "/api", method = RequestMethod.POST)
public void messages(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.write("dfsfd");
writer.flush();
}
#Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(myFilter());
registration.addUrlPatterns("/service/example/get/*");
registration.setOrder(1);
return registration;
}
#Bean(name = "someFilter")
public GenericFilterBean myFilter() {
return new MyFilter();
}
}
MyFilter class:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.web.filter.GenericFilterBean;
public class MyFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("Filter called");
filterChain.doFilter(servletRequest, servletResponse);
}
}
try to call 3 services:
http://localhost:8080/service/example/get/test
http://localhost:8080/service/example/get/test1
http://localhost:8080/service/example/api
and check the printed log.
you can control the url patter using this line
registration.addUrlPatterns("/service/example/get/*");
I hope this sample help you, thanks

SpringBoot - Filters exception handler

I have a spring-boot application. I have a class that is ControllerAdvice to handle the exceptions thrown by the app.
I created a filter that I want to use it to validate a header for all my requests. When I throw the custom exception from that filter, it does not go through my exception handler, it uses the default Spring error control.
Is there any way to handle the errors from filters in my own way?
If you catch the exception in a Filter, then handle with HandlerExceptionResolver, the #ControllerAdvice will start to work
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Component
public class HystrixRequestContextServletFilter extends OncePerRequestFilter {
private static final String API_KEY = "apiKey";
private final HandlerExceptionResolver handlerExceptionResolver;
#Autowired
public HystrixRequestContextServletFilter(HandlerExceptionResolver handlerExceptionResolver) {
this.handlerExceptionResolver = handlerExceptionResolver;
}
#Override
public void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
String apiKey = httpRequest.getHeader(API_KEY);
if (apiKey == null) {
throw new AuthenticationException("no apikey in request, path [" + httpRequest.getRequestURI() + "]");
}
ApiKeyHystrixRequestVariable.set(apiKey);
filterChain.doFilter(httpRequest, httpResponse);
} catch (Exception e) {
handlerExceptionResolver.resolveException(httpRequest, httpResponse, null, e);
} finally {
context.shutdown();
}
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getRequestURI().startsWith("/internal");
}
}
You can write a controller that extends BasicErrorController and write a method that has a #GetMapping annotation like this:
#RestController
public class FilterExceptionController extends BasicErrorController {
private static final Logger LOGGER = LoggerFactory.getLogger(FilterExceptionController.class);
public FilterExceptionController(){
super(new DefaultErrorAttributes(),new ErrorProperties());
}
#GetMapping
private <T> ResponseResult<T> serviceExceptionHandler(HttpServletRequest request) {
Map<String,Object> body= getErrorAttributes(request,isIncludeStackTrace(request,MediaType.ALL));
String message = String.valueOf(body.get("message"));
body.forEach((k,v)->LOGGER.info("{} ==> {}",k,v));
return RestResultGenerator.genError(500,"Filter Error Occurred, Message: [ "+message+" ]");
}
#Override
public String getErrorPath() {
return "/error";
}
}
There is my test filter:
#WebFilter(filterName = "ipFilter",urlPatterns = "/*")
public class IpFilter implements Filter{
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(!Arrays.asList(new String[]{"127.0.0.1"}).contains(servletRequest.getRemoteAddr())){
throw new ServiceException(403,"ip forbid");
}
}
#Override
public void destroy() {
}
}
This is result(but only get exception message not code):
enter image description here

spring websocket+rabbitmq stomp:How can i get all online user and disconnect some user’s connect?

If spring store user's websocket session,how can get it,if i use someHandler to put session in a map,that mean map‘s size maybe bigger than 10w+,I don't think it good。
SessionHandler.java
package hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
#Service
public class SessionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionHandler.class);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
public SessionHandler() {
scheduler.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
sessionMap.keySet().forEach(k -> {
try {
sessionMap.get(k).close();
sessionMap.remove(k);
} catch (IOException e) {
LOGGER.error("Error while closing websocket session: {}", e);
}
});
}
}, 10, 10, TimeUnit.SECONDS);
}
public void register(WebSocketSession session) {
sessionMap.put(session.getId(), session);
}
}
like this:
https://github.com/isaranchuk/spring-websocket-disconnect/blob/master/src/main/java/hello/SessionHandler.java
Implement the HandshakeInter as below.
public class HttpSessionIdHandshakeInterceptor implements HandshakeInterceptor {
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes)
throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
attributes.put(SESSION_ATTR, session.getId());
}
}
return true;
}
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception ex) {
}
}
Then Add to the endpoint.
public class WebSocketConfig extends
AbstractWebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio")
.withSockJS()
.setInterceptors(new HttpSessionIdHandshakeInterceptor());
}
...
}
Next we can create a ChannelInterceptorAdapter that uses the session id to update the last accessed time using Spring Session. For example:
#Bean public ChannelInterceptorAdapter sessionContextChannelInterceptorAdapter() {
return new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
String sessionId = (String) sessionHeaders.get(SESSION_ATTR);
if (sessionId != null) {
Session session = sessionRepository.getSession(sessionId);
if (session != null) {
sessionRepository.save(session);
}
}
return super.preSend(message, channel);
}
};
}

RESTEASY -Spring Exception mapper for javax.ws.rs.NotFoundException is not getting Involed

Hi I am using Reasteasy 3 along spring 3.
I have defined multiple Exception mappers for my project, and most of them are getting invoked whenever concerned Exceptions are raised.
But I am facing problem with **“javax.ws.rs.NotFoundException” when this exception is raised its handler is not getting invoked.**
All Exception mappers including mapper for “NotFoundException” exception are defined/configured in same manner and they are getting invoked except mapper for NotFoundException.
Is there any different way to configure Exception mappers for JAX-RS exception ..please help I am stuck here .
code for Exception mappers
package com.xyz.exception;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class CCDBNotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
#Override
public Response toResponse(NotFoundException e) {
// Logs and code
}
}
other Exception mapper configured and defined in a Excatly similar way are getting Invoked ... see snap shot for working Exception mapper
package com.xyz.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException;
#Provider
public class CCDBJAXBUnmarshalExceptionMapper implements ExceptionMapper<JAXBUnmarshalException> {
#Override
public Response toResponse(NotFoundException e) {
// Logs and code
}
}
package com.xyz.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class CCDBApplicationExceptionMapper implements ExceptionMapper<CCDBApplicationException>{
#Override
public Response toResponse(NotFoundException e) {
// Logs and code
}
}
Spring Configuration for provider annotation
<!-- Auto Reegistry of RESTEasy providers -->
<context:component-scan base-package="com.xyz">
<context:include-filter type="annotation" expression="javax.ws.rs.ext.Provider"/>
</context:component-scan>
I tried everything but Exception mappers for any "javax.ws.rs.*" exceptions are not getting invokes although they are getting registered .
to overcome this problem i have used servlet filter ... i posting my code to help others whoever is facing same issues
Register below filter in your web.xml file.
filter code :
public class CCDBExceptionFilter implements Filter {
Logger logger = Logger.getLogger(CCDBExceptionFilter.class);
private static final String NOTFOUND_MESSAGE = "{\"ErrorResponse\":{\"errorCode\":1,\"errorMessage\":\"Invalid request, There is no service configured to handle provided request\"}}";
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("CCDBExceptionFilter got initiated" );
}
public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
logger.debug("Request recived to process " + req.getPathInfo());
DummyResponse dummyResponse = new DummyResponse((HttpServletResponse)response);
filterChain.doFilter(request, dummyResponse);
logger.debug("Request handled for " + req.getPathInfo());
if(404 == dummyResponse.getErrorcode()){
logger.debug("Found 404 error code" );
res.setStatus(400);
PrintWriter out = response.getWriter();
out.println(NOTFOUND_MESSAGE);
out.close();
}
}
public void destroy() {
}
}
Dummy Response class
public class DummyResponse extends HttpServletResponseWrapper {
Logger logger = Logger.getLogger(DummyResponse.class);
private CharArrayWriter buffer; // This can be used as an Writer
int errorcode;
public int getErrorcode() {
return errorcode;
}
public void setErrorcode(int errorcode) {
this.errorcode = errorcode;
}
public DummyResponse(HttpServletResponse response) {
super(response);
buffer = new CharArrayWriter();
}
public String toString() {
return buffer.toString();
}
public PrintWriter getWriter() {
return new PrintWriter(buffer);
}
public void setStatus(int sc) {
if (404 == sc) {
setErrorcode(404);
super.setStatus(400);
} else {
super.setStatus(sc);
}
}
public void sendError(int sc, String msg) throws IOException {
if (404 == sc) {
setErrorcode(404);
super.setStatus(400);
} else {
super.setStatus(sc);
}
}
public void sendError(int sc) throws IOException {
if (404 == sc) {
setErrorcode(404);
super.setStatus(400);
} else {
super.setStatus(sc);
}
}
}

Resources