Search code examples
javajava-ee-6netflix-zuul

How to proxy a request to another location with enriched header in a java EE application


I'm facing the following problem. I need to create a way in a java EE application running on websphere (without Spring) to proxy a request to another location and enrich the header with a bearer token.

take following example

GET request: http://servicehost.com/proxy/targetapi/userresource

needs to be forwarded to

GET request: http://othertargethost.com/targetapi/userresource with Authorization: Bearer randomtoken

I solved this problem in another application but this was a spring boot application using Netflix Zuul and spring-cloud-starter-netflix-zuul.

However now I'm in a strict EE context no spring allowed at all. I have not found any good documentation or examples on how to setup or configure netflix zuul in a pure EE context.

Which other alternatives do I have to solve this problem ? I was thinking on the following

  • Setup a Servlet on **/proxy/* and create a filter that will do the forwarding
  • Search the internet for something akin to Zuul with better documentation to run it in EE
  • ...

I could really appreciate anything pointing me in the right direction.

Jersey web service proxy is not a solution for me since this is pinpointed on a specific endpoint and with a specific http method

GET request: http://servicehost.com/proxy/targetapi/userresource

could be

GET request: http://servicehost.com/proxy/targetapi/contractresource

or

GET request: http://servicehost.com/proxy/specialapi/userresource

and it needs to be able to handle GET, POST, PUT and DELETE


Solution

  • I wasn't able to use Zuul in EE so I only had one recourse and that is write my own servlet

    @WebServlet(name = "ProxyServlet", urlPatterns = {"/proxy/*"})
    public class ProxyServlet extends HttpServlet {
    
        public static final String SESSION_ID_PARAM = "delijnSessionId";
    
        @Inject
        private Logger logger;
    
        @Inject
        private ProxyProperties proxyProperties;
    
        @Inject
        private SecurityService securityService;
    
        @Inject
        private ProxyHttpClientFactory proxyHttpClientFactory;
    
        @Inject
        private StreamUtils streamUtils;
    
        @Override
        protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            proxy(httpServletRequest, httpServletResponse);
        }
    
        @Override
        protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            proxy(httpServletRequest, httpServletResponse);
        }
    
        @Override
        protected void doPut(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            proxy(httpServletRequest, httpServletResponse);
        }
    
        @Override
        protected void doDelete(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            proxy(httpServletRequest, httpServletResponse);
        }
    
        private void proxy(HttpServletRequest request, HttpServletResponse response) {
            try {
                String requestUrl = request.getRequestURI();
                String method = request.getMethod();
                String sessionId = getSessionId(request);
    
                String protocol = proxyProperties.getProperty(ProxyProperties.PROXY_PROTOCOL);
                String server = proxyProperties.getProperty(ProxyProperties.PROXY_SERVER);
                String port = proxyProperties.getProperty(ProxyProperties.PROXY_PORT);
    
                String newPath = requestUrl.replaceFirst(".*/proxy", "");
    
                URI uri = new URI(protocol, null, server, Integer.parseInt(port), newPath, request.getQueryString(), null);
    
                ProxyHttpMethod proxyRequest = new ProxyHttpMethod(method);
                proxyRequest.setURI(uri);
                copyBodyFromRequest(request, method, proxyRequest);
                copyHeadersFromRequest(request, proxyRequest);
                enrichWithAccessToken(proxyRequest, sessionId);
    
                try (CloseableHttpClient client = proxyHttpClientFactory.produce()) {
                    logger.info("uri [{}]", uri);
                    logger.info("method [{}]", method);
                    execute(client, proxyRequest, response);
                } catch (IOException e) {
                    throw new TechnicalException(e);
                }
            } catch (URISyntaxException | IOException e) {
                throw new TechnicalException(e);
            }
        }
    
        private void execute(CloseableHttpClient client, ProxyHttpMethod proxyHttpMethod, HttpServletResponse response) {
            try (CloseableHttpResponse proxyResponse = client.execute(proxyHttpMethod)) {
                int statusCode = proxyResponse.getStatusLine().getStatusCode();
                if (statusCode >= 200 || statusCode < 300) {
                    response.setStatus(statusCode);
                    HttpEntity entity = proxyResponse.getEntity();
                    if(entity != null){
                        String result = streamUtils.getStringFromStream(entity.getContent());
                        logger.trace("result [" + result + "]");
                        response.getWriter().write(result);
                        response.getWriter().flush();
                        response.getWriter().close();
                    }
                } else {
                    throw new TechnicalException("[" + statusCode + "] Error retrieving access token");
                }
            } catch (IOException e) {
                throw new TechnicalException(e);
            }
        }
    
        private void enrichWithAccessToken(ProxyHttpMethod proxyRequest, String sessionId) {
            Optional<TokenDto> token = securityService.findTokenBySessionIdWithRefresh(sessionId);
            if (token.isPresent()) {
                String accessToken = token.get().getAccessToken();
                logger.trace(String.format("Enriching headers with: Authorization Bearer %s", accessToken));
                proxyRequest.setHeader("Authorization", "Bearer " + accessToken);
            } else {
                logger.info(String.format("No token found in repository for sessionId [%s]", sessionId));
                throw new RuntimeException("No token found in repository");
            }
        }
    
        private void copyBodyFromRequest(HttpServletRequest request, String method, ProxyHttpMethod proxyRequest) throws IOException {
            if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
                String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
                StringEntity entity = new StringEntity(body);
                proxyRequest.setEntity(entity);
            }
        }
    
        private void copyHeadersFromRequest(HttpServletRequest request, ProxyHttpMethod proxyRequest) {
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                if (!"host".equalsIgnoreCase(headerName) && !"Content-Length".equalsIgnoreCase(headerName)) {
                    proxyRequest.setHeader(headerName, request.getHeader(headerName));
                }
            }
        }
    
        private String getSessionId(HttpServletRequest request) {
            String sessionId = "";
            Cookie[] cookies = request.getCookies();
            if(cookies != null){
                for (Cookie cookie : cookies) {
                    if (SESSION_ID_PARAM.equals(cookie.getName())) {
                        sessionId = cookie.getValue();
                    }
                }
                return sessionId;
            }
            return "";
        }
    }
    

    not ideal but I didn't see another way out