Search code examples
javaservletshaproxyaudit-logging

How to capture the server used to fulfill a request when incoming traffic is using Load Balancer URL?


I have a Spring Boot Java REST application with many APIs exposed to our clients and UI. I was tasked with implementing a Transaction logging framework that will capture the incoming transactions along with the response we send.

I have this working with Spring AOP and an Around inspect and I'm currently utilizing the HttpServletRequest and HttpServletResponse objects to obtain a lot of the data I need.

From my local system I am not having any issues capturing the server used since I'm connecting to my system directly. However, once I deployed my code I saw that the load balancer URL was being captured instead of the actual server name.

I am also using Eureka to discover the API by name as it's only a single application running on HAProxy.

Imagine this flow:

 /*
 UI -> https://my-lb-url/service-sidecar/createUser

 HAProxy directs traffic to -> my-lb-url/service-sidecar/ to one of below:
      my-server-1:12345
      my-server-2:12345
      my-server-3:12345


 Goal  : http://my-server-1:1235/createUser
 Actual: https://my-lb-url/createUser

Here is the code I am using to get the incoming URL.

 String url = httpRequest.getRequestURL().toString();
 if(httpRequest.getQueryString() != null){
      transaction.setApi(url + "?" + httpRequest.getQueryString());
 } else {
      transaction.setApi(url);
 }

Note:

I am not as familiar with HAProxy/Eurkea/etc. as I would like to be. If something stated above seems off or wrong then I apologize. Our system admin configured those and locked the developers out.


UPDATE

This is the new code I am using to construct the Request URL, but I am still seeing the output the same.

// Utility Class

public static String constructRequestURL(HttpServletRequest httpRequest) {

    StringBuilder url = new StringBuilder(httpRequest.getScheme());
    url.append("://").append(httpRequest.getServerName());

    int port = httpRequest.getServerPort();
    if(port != 80 && port != 443) {
        url.append(":").append(port);
    }
    url.append(httpRequest.getContextPath()).append(httpRequest.getServletPath());

    if(httpRequest.getPathInfo() != null) {
        url.append(httpRequest.getPathInfo());
    }
    if(httpRequest.getQueryString() != null) {
        url.append("?").append(httpRequest.getQueryString());
    }
    return url.toString();
}

// Service Class

transaction.setApi(CommonUtil.constructRequestURL(httpRequest));

Solution

  • I found a solution to this issue, but it's not the cleanest route and I would gladly take another suggestion if possible.

    1. I am autowiring the port number from my application.yml.
    2. I am running the "hostname" command on the Linux server that is hosting the application to determine the server fulfilling the request.

    Now the URL stored in the Transaction Logs is accurate.

    --

    @Autowired
    private int serverPort; 
    
    /*
     * ... 
     */
    
    private String constructRequestURL(HttpServletRequest httpRequest) {
    
        StringBuilder url = new StringBuilder(httpRequest.getScheme())
                .append("://").append(findHostnameFromServer()).append(":").append(serverPort)
                .append(httpRequest.getContextPath()).append(httpRequest.getServletPath());
    
        if(httpRequest.getPathInfo() != null) {
            url.append(httpRequest.getPathInfo());
        }
        if(httpRequest.getQueryString() != null) {
            url.append("?").append(httpRequest.getQueryString());
        }
        return url.toString();
    }
    
    private String findHostnameFromServer(){
    
        String hostname = null;
        LOGGER.info("Attempting to Find Hostname from Server...");
        try {
            Process process = Runtime.getRuntime().exec(new String[]{"hostname"});
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                hostname = reader.readLine();
            }
    
        } catch (IOException e) {
            LOGGER.error(CommonUtil.ERROR, e);
        }
        LOGGER.info("Found Hostname: {}", hostname);
        return hostname;
    }