How to get server details from HttpServletRequest

I want to get the host and scheme of the running server.

This is what I used:

request.getHeader("Host")
request.getScheme()

This work when running locally. However when testing on https://qa-refapp.openmrs.org/, this is what it returns

request.getHeader(“Host”) returns >> 127.0.0.1 request.getScheme() returns >> http

As you see the both values are wrong. The expected values were qa-refapp.openmrs.org, and https

I heard that such issues could cause when behind a reverse proxy or load balancer. Is that the case with qa-refapp?

If that so, how could I get the correct values?

FYI: https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpServletRequest.html

Is the IP address returned by InetAddress.getLocalHost().getHostAddress() or InetAddress.getLocalHost().getHostName() of help? That being said, what is the use case?

@dkayiwa I want to set correct values in here.

In qa-refapp the value returned for ‘request.getHeader(HttpHeaders.HOST)’ is 127.0.0.1

Is there any server instance that I could remote debug, so I could find if your suggested methods return the correct values.

Let me know if there is any other workaround.

Before you remote debug them, do they work locally?

They do work locally. It successfully returns localhost:port as the host and http as the scheme.

I think the problem may be with the way Openmrs servers are configured. Is it possible to remote debug one of the servers?

Commit and we shall test it on qa-refapp.

There is a GP for the rest url prefix which has the scheme and host info, why can’t you use that?

You mean by retrieving the value of webservices.rest.uriPrefix?

To answer a question in your initial post, yes qa-refapp.openmrs.org is a docker deployment on som machine with multiple deployments. So it’s possible that a proxy is forwarding/mapping requests. One approach you could take is to set up a similar deployment locally, and then you can actually use a debugger in tomcat to see whether the original request parameters can be accessed in some way.

Or maybe @cintiadr knows if there’s a config setting we’d have to change so that the application running on qa-refapp (or similar) can know what is the original address that a client is requesting?

Yes, that’s what I meant

The value returning for qa-refapp.openmrs.org is http://127.0.0.1/openmr..

127.0.0.1 is not what we need.

Try this piece of code:

public static String getHostName() {
	InetAddress addr;
	try {
		addr = InetAddress.getLocalHost();
		return addr.getHostName();
	} catch (UnknownHostException e) {
		LOG.error("Hostname can not be resolved", e);
		return null;
	}
}

@darius is absolutely right here. We do have nginx in front of qa-refapp. This is actually what I think the relevant piece:

location / {
       return 301 https://qa-refapp.openmrs.org/openmrs;
 }
 location /openmrs {
   proxy_pass http://127.0.0.1:8081/openmrs;
 }

Indeed, I’m not setting any headers on the proxy. Usually we do something like this if the backend cares about headers:

proxy_set_header       Host $host;  
proxy_set_header  X-Real-IP  $remote_addr;  
proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;  
proxy_set_header X-Forwarded-Proto  $scheme;  

(Copied from java - How to get host name with port from a http or https request - Stack Overflow, but it’s something I’ve done a couple of times already)

Would that be good enough for you? Regardless, it would be something we should add to installation documentation.

(Sorry for the delay, I’ve lost control of my email recently)

1 Like

Just in case, if you want me to do that change (or actually, configure any header you want), please raise an ITSM ticket in our JIRA. It should be pretty easy to do, and after doing for qa-refapp we can deploy to all other openmrs test instances.

@cintiadr Thanks, the information was really helpful. So this is my suggested code to retrieve original host and the port.

String host = request.getHeader(HttpHeaders.X-Forwarded-Host);
if (host == null) {
    host = request.getHeader(HttpHeaders.HOST);
}

		
String scheme = request.getHeader(HttpHeaders.X_FORWARDED_PROTO);
if (scheme == null) {
    scheme = request.getScheme();
}

Does this look okay? If so, we will need X-Forwarded-Host, X_FORWARDED_PROTO headers set on the proxy, right?

I did the change to the nginx on that machine. So I’d expect the following to work now:

String host =  request.getHeader(HttpHeaders.HOST);
	
String scheme = request.getHeader(HttpHeaders.X_FORWARDED_PROTO);
if (scheme == null) {
    scheme = request.getScheme();
}

I think ‘X-Forwarded-Host’ is not what you are looking for.

The above request sets the "Host" header to the $host variable, which should contain information about the original host being requested. The X-Forwarded-Proto header gives the proxied server information about the schema of the original client request (whether it was an http or an https request).

The X-Real-IP is set to the IP address of the client so that the proxy can correctly make decisions or log based on this information. The X-Forwarded-For header is a list containing the IP addresses of every server the client has been proxied through up to this point. In the example above, we set this to the $proxy_add_x_forwarded_for variable. This variable takes the value of the original X-Forwarded-For header retrieved from the client and adds the Nginx server's IP address to the end.
1 Like

Thanks for the help. We will test the changes on qa-refapp.

1 Like

I suggested using InetAddress.getLocalHost, did you try it?

As a rule, the hostname of the machine is rarely the host that the user requested in the wild.

For example, qa-refapp.openmrs.org is hosted on a machine which is called ‘garissa’. I have at least other 3 DNS entries pointing to that server, each one serving a different service.

The hostname is something we change reasonably frequently, in fact it’s very common to move the DNS to a new machine, with a different hostname. Unless you are attempting to know which node of your HA application handled the application, the hostname tends to not be relevant for the application.

From that same stackoverflow:

Any attempt to determine the hostname by an IP address like this

InetAddress.getLocalHost().getHostName()
is bound to fail in some circumstances:

- The IP address might not resolve into any name. Bad DNS setup, bad system setup or bad provider setup may be the reason for this.
- A name in DNS can have many aliases called CNAMEs. These can only be resolved in one direction properly: name to address. The reverse direction is ambiguous. Which one is the "official" name?
- A host can have many different IP addresses - and each address can have many different names. Two common cases are: One ethernet port has several "logical" IP addresses or the computer has several ethernet ports. It is configurable whether they share an IP or have different IPs. This is called "multihomed".
- One Name in DNS can resolve to several IP Addresses. And not all of those addresses must be located on the same computer! (Usecase: A simple form of load-balancing)
Let's not even start talking about dynamic IP addresses.

I’m not sure our DNS provider setups up reverse DNS. I clearly don’t do it manually, so I never really bothered as we don’t really host an SMTP or anything. We use heaps of CNAME (as one must :D). We don’t even have HA nor anything, even though this is bound to work only on a very very specific scenario.