Create a reverse proxy by littleproxy - java

I'm a beginner with littleproxy, how can I create a reverse proxy server?
My proxy get requests from clients and sends them to servers (servers only a regular site same as www.xxx.com contain only web page(in not rest) and proxy get response from server(a web page) and return to client.
For example, client url is localhost:8080/x, proxy maps it to www.myserver.com/xy and shows xy page for client. How can do it by using a filter or a httpservlet.
My http servlet will be as follow:
public class ProxyFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpProxyServer server =
DefaultHttpProxyServer.bootstrap()
.withPort(8080)
.withFiltersSource(new HttpFiltersSourceAdapter() {
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
return new HttpFiltersAdapter(originalRequest) {
#Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
// TODO: implement your filtering here ????
return null;
}
#Override
public HttpResponse proxyToServerRequest(HttpObject httpObject) {
// TODO: implement your filtering here ????
return null;
}
#Override
public HttpObject serverToProxyResponse(HttpObject httpObject) {
// TODO: implement your filtering here ????
return httpObject;
}
#Override
public HttpObject proxyToClientResponse(HttpObject httpObject) {
// TODO: implement your filtering here ????
return httpObject;
}
};
}
})
.start();
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}

LittleProxy uses Host header to do the routing. So simplest thing you can do is set Host as the real server in clientToProxyRequest method.
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
if(httpObject instanceof FullHttpRequest) {
FullHttpRequest httpRequest = (FullHttpRequest)httpObject;
httpRequest.headers().remove("Host");
httpRequest.headers().add("Host", "myserver.com:8080");
}
return null;
}

Related

Not able to override cookie

I am using Servlet Filter to intercept request which has cookies present and then trying to override specific cookie value using ThreadLocal.
But the overridden value is not getting reflected. Also, it is not able to add a new cookie when tried. Couldn't able to figure out what I am doing wrong.
Controller Class has two get endpoints. With cookie endpoint I am trying to add a cookie in the response, to intercept and test it when I hit override endpoint afterwards.
#RestController
public class FilterController {
#Autowired
AlphaCookie alphaCookie;
#Autowired
AlphaCookieFilter alphaCookieFilter;
#GetMapping("override") // testing endpoint for overriding cookie
public String cookieTest() {
this.alphaCookieFilter.persist(new AlphaCookie("newValue"));
return "Cookie Overriden, { AlphaCookie: newValue }";
}
#GetMapping("cookie") // to add cookie for testing
public String addCookieToBrowser(HttpServletResponse httpServletResponse) {
Cookie cookie = new Cookie("AlphaCookie", "oldValue");
cookie.setMaxAge(3600);
httpServletResponse.addCookie(cookie);
return "Cookie added, { AlphaCookie: old }";
}
}
Filter to intercept the request and check for the specific cookie, also override the cookie
#Component("alphaCookieFilter")
public class AlphaCookieFilter implements Filter {
#Autowired
private ApplicationContext applicationContext;
public static final ThreadLocal<HttpServletResponse> RESPONSE_HOLDER = new ThreadLocal<>();
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
RESPONSE_HOLDER.set((HttpServletResponse) response);
String activeCookie = null;
if (httpServletRequest.getCookies() != null) {
for (Cookie cookie: httpServletRequest.getCookies()) {
if ("AlphaCookie".equals(cookie.getName())) {
activeCookie = cookie.getValue();
}
}
}
this.applicationContext.getBean("alphaCookie", AlphaCookie.class)
.override(new AlphaCookie(activeCookie));
chain.doFilter(request, response);
RESPONSE_HOLDER.remove();
}
public void persist(AlphaCookie alphaCookie) {
Cookie cookie = new Cookie("AlphaCookie", alphaCookie.getActiveCookie());
cookie.setDomain("code.org");
cookie.setPath("/");
cookie.setMaxAge(-1);
cookie.setSecure(true);
cookie.setHttpOnly(true);
RESPONSE_HOLDER.get().addCookie(cookie);
}
}
POJO to store the cookie value
#Component("alphaCookie")
public class AlphaCookie implements Serializable {
private static final long serialVersionUID = 1L;
private String activeCookie;
public AlphaCookie() {
super();
}
public AlphaCookie(String activeCookie) {
this();
this.activeCookie = activeCookie;
}
public void override(AlphaCookie alphaCookie) {
synchronized (this) {
this.activeCookie = alphaCookie.activeCookie;
}
}
public String getActiveCookie() {
return this.activeCookie;
}
}
On debugging I found below properites are not allowing me to override the cookie value.
cookie.setDomain("code.org");
cookie.setSecure(true);
---------Resolved-----------
For local testing
We should always set the domain name to localhost
set cookie.setSecure to false as localhost is not secure protocol (not HTTPS)

How to get ServerHttpRequest/ServerHttpResponse body to string

Want to implement a proxy on top of Spring API Gateway to log requests/responses.
I defined my own filters for incoming request and outcoming responses.
REQUEST GATEWAY FILTER:
public class RequestGatewayFilter extends AbstractGatewayFilterFactory<RequestGatewayFilter.Config> {
private static final Logger logger = LogManager.getLogger(RequestGatewayFilter.class);
public RequestGatewayFilter() {
super(Config.class);
}
#Autowired
CustomProxyLogger customLogger;
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest.Builder builder = exchange.getRequest().mutate()
.header(PR_CORRELATION_ID, UUID.randomUUID().toString());
ServerHttpRequest request = builder.build();
customLogger.logRequest(logger, request);
return chain.filter(exchange.mutate().request(request).build());
};
}
}
RESPONSE GATEWAY FILTER:
public class ResponseGatewayFilter extends AbstractGatewayFilterFactory<ResponseGatewayFilter.Config> {
private static final Logger logger = LogManager.getLogger(ResponseGatewayFilter.class);
public ResponseGatewayFilter() {
super(Config.class);
}
#Autowired
CustomProxyLogger customLogger;
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
customLogger.logResponse(logger, exchange);
}));
};
}
I have to log request and response body. I tried several ways for example as explained on How to correctly read Flux<DataBuffer> and convert it to a single inputStream
In this case the map function did not execute anytime.
Also tried casting ServerHttpRequest to HttpServletRequest in order to get body from there but this throws Cast Exception.
None worked....
Any ideas or possible approaches to solve this 'get body' problem ?
Simply create another one Filter and log all requests, responses.
#Component
public class RequestResponseLoggingFilter implements Filter {
...
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
LOG.info(request);
chain.doFilter(request, response);
LOG.info(response);
}
}

Required request body is missing after making a copy using HttpServletRequestWrapper

In my project, I have a set of api calls which should filtered through certain set of common validation. In that case, I have to intercept the request before it hits the REST controller, read the request body, do the validations and pass it to the controller if the request passes the validations.
Since the HttpServletRequest cannot be deserialized more than once, I used a HttpServletRequestWrapper to make a copy of the actual request. Using the copy it makes, I do the validations.
Following is the configuration class for intercepting the requests.
public class InterceptorConfig extends WebMvcConfigurerAdapter {
#Autowired
CustomInterceptor customInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor).addPathPatterns("/signup/**");
}
}
Here is my preHandle method inside CustomInterceptor class which extends HandlerInterceptorAdaptor
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
return false;
}
// some more validations which end up returning false if they are met
}
return true;
}
This is my HttpRequestWrapper
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public HttpRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
try {
requestBody = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
requestBody = new byte[0];
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
public int read () throws IOException {
return byteArrayInputStream.read();
}
};
}
}
All set now. Now, when I send a request to any url with the pattern of /signup/**, all the validations are happening fine. However, once the request hits the controller method, error pops out saying the request body is not available.
Required request body is missing: public
com.mypackage.myResponseObject
com.mypackage.myController.myControllerMethod(com.mypackage.myDTO)
I am struggling to find the reason for this and also a way to overcome the issue. Is there anything I have done wrong in RequestWrapper class? or anything missing?
Help me to sort this thing out.
Thanks!
The Problem seems to be that you are using an Interceptor to read the HttpServletRequest's InputStream and just wrap it in HttpRequestWrapper but the wrapper is never returned.
I think you should use a Filter
public class CustomFilter extends OncePerRequestFilter {
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
//return false;
}
// some more validations which end up returning false if they are met
}
filterChain.doFilter(copiedRequest, (ServletResponse) response);
}
}
And you need to use this Filter in either web.xml or WebApplicationInitializer

Consuming HttpServletRequest Multiple Times and Chain Between Methods

I have to read HttpServletRequest multiple times. I have wrapped HttpServletRequest like said in those posts Http Servlet request lose params from POST body after read it once
In my filter class which extends AbstractAuthenticationProcessingFilter, i can consume and chain request in successfulAuthentication method since it has chain parameter. But in addition to those solutions i have to chain request between attempt and succesful authentication steps:
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
// wrapping request and consuming
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// Since i couldn't chain wrapped httpServletRequest from attemptAuthentication step, this request still gets non-wrapping one and inputstream is empty
}
How can i pass wrapped request from attemptAuthentication to successfulAuthentication?
This is an old question but in case someone gets the same problem:
You can wrap a request like suggested in the previous answer but you need to wrap it before it gets filtered by Authentication filter:
Spring security configuration will look something like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
AuthFilter authFilter = new AuthFilter();
WrapperFilter wrapperFilter = new WrapperFilter();
http .cors()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(exceptionHandler)
.and()
.authorizeRequests()
.antMatchers("/v1/*", "/api/*")
.authenticated()
.and()
.addFilterBefore(authFilter, BasicAuthenticationFilter.class)
.addFilterBefore(wrapperFilter, AuthFilter.class);
}
So that wrapper filter goes before your auth filter and wrapper filter's doFilter wraps the request and passes it on:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
chain.doFilter(wrapper, response);
}
And MultiReadHttpServletRequest is the following:
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
try {
body = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
body = new byte[0];
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
ByteArrayInputStream wrapperStream = new ByteArrayInputStream(body);
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return wrapperStream.read();
}
};
}
}
There is no such way. The only thing you can do is wrap the request every time you gonna read it in one more filter. Just wrap the request every time before you read it copying body.
See some working code for a wrapper e.g. here

Tomcat, Spring MVC, redirecting users to 'www' without using htaccess file

I am trying to append www. to the domain but I can't find a solution. I found the results using .htaccess file but this solution works with Apache server but I am working with Tomcat.
For eg.
When user types in: abcdomain.com
then it should redirects to the : www.abcdomain.com
Any help would greatly appreciated.
The one thing that comes to mind would be a Filter processing the requests before they hit your app:
public class RedirectFilter implements Filter {
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain)
throws IOException, ServletException {
if(arg0 instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest)arg0;
String url = req.getRequestURL().toString()+"?"+req.getQueryString();
Pattern p = Pattern.compile("(?i)(http(s?)://)www\\.");
Matcher m = p.matcher(url);
if(m.find()) {
//www is present -> continue
chain.doFilter(arg0, arg1);
} else {
StringBuilder wwwurl = new StringBuilder();
if(url.toLowerCase().startsWith("http://")) {
wwwurl.append("http://www.").append(url.substring(7));
} else if(url.toLowerCase().startsWith("https://")) {
wwwurl.append("https://www.").append(url.substring(8));
}
((HttpServletResponse)arg1).sendRedirect(wwwurl.toString());
}
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}

Categories