Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gather information from HTTP headers #58

Open
robertvazan opened this issue Sep 19, 2018 · 4 comments
Open

Gather information from HTTP headers #58

robertvazan opened this issue Sep 19, 2018 · 4 comments

Comments

@robertvazan
Copy link

I am using this library server-side and I could use some easy way to gather basic information from HTTP headers. I am currently doing this:

HttpServletRequest request = ...;
hit.userIp(ip(request.getHeader("X-Forwarded-For")));
hit.anonymizeIp(true);
hit.userLanguage(language(request.getHeader("Accept-Language")));
hit.userAgent(request.getHeader("User-Agent"));
hit.documentUrl(request.getRequestURL().toString());
hit.documentTitle(request.getRequestURI());

This uses two helper methods. Method ip() decodes IP address from X-Forwarded-For if available:

public static String ip(String xff) {
	if (xff == null)
		return null;
	/*
	 * X-Forwarded-For may contain multiple IP addresses.
	 * In this case the first recognizable address is the real one.
	 */
	for (String candidate : Arrays.stream(xff.split(",")).map(String::trim).collect(toList())) {
		try {
			/*
			 * We want to skip junk IP addresses, which might have found their way
			 * into X-Forwarded-For via various localhost, company, or ISP proxies.
			 * We will be weeding out IP addresses that cannot be parsed
			 * or that belong in some special address range.
			 */
			InetAddress ip = InetAddress.getByName(candidate);
			if (ip.isSiteLocalAddress() || ip.isLinkLocalAddress() || ip.isLoopbackAddress() || ip.isMulticastAddress())
				continue;
			if (ip instanceof Inet6Address && ip.getAddress()[0] == (byte)0xfd)
				continue;
			byte[] address = ip.getAddress();
			if (IntStream.rangeClosed(0, address.length).allMatch(i -> address[i] == 0))
				continue;
			/*
			 * Perform local IP address anonymization.
			 * We are also instructing GA to anonymize IPs on GA end,
			 * but it is technically and legally safer to anonymize locally.
			 * Anonymization rules are the same as those used by GA,
			 * i.e. zero last 80 bits of IPv6 and last 8 bits of IPv4.
			 */
			int anonymizedBytes = ip instanceof Inet6Address ? 10 : 1;
			for (int i = 0; i < anonymizedBytes; ++i)
				address[address.length - i - 1] = 0;
			return InetAddress.getByAddress(address).getHostAddress();
		} catch (Throwable t) {
		}
	}
	return null;
}

Method language parses language from request's Accept-Language header:

/*
 * This helper method takes contents of Accept-Language header and
 * returns user's language in the 'en-US' or 'en' format.
 */
private static String language(String accepts) {
	if (accepts == null)
		return null;
	/*
	 * The header can list multiple languages, so we just pick the first one.
	 */
	String[] alternatives = accepts.split(",");
	if (alternatives.length == 0)
		return null;
	String first = alternatives[0];
	/*
	 * If there's priority attached to the language (e.g. 'en-US;q=0.5'),
	 * we strip the priority field.
	 */
	String[] parts = first.split(";");
	if (parts.length == 0)
		return null;
	return parts[0].trim().toLowerCase();
}

Please consider adding this functionality into the library, so that people don't have to reimplement it for every project. Thanks!

@brsanthu
Copy link
Owner

I'm afraid adding this functionality will introduce dependency servlet package, which can be a concern for java 9 based apps. We could think about adding this to new repo so they could include this if require.

Or we could make it load Servlet package based on Class.forName and if loaded use it, else ignore it.

@robertvazan
Copy link
Author

You could declare maven dependency on javax.servlet-api as provided. You could then code easily against servlet API without ugly workarounds like Class.forName(). GAJ's maven artifact would not directly depend on javax.servlet-api artifact, but it could use it if the application depends on it. In order to prevent NoClassDefFoundError, this functionality would have to be lazily loaded, which can be accomplished by isolating it in separate class.

BTW, I don't understand why you singled out Java 9. Is there some change in Java 9 that would make this more complicated?

@brsanthu
Copy link
Owner

In Java 9 each library should specify which modules they need and java will allow only those modules. So for this library to work fine in Java 9 enforced runtime, if we don't add module which allows servlet, then library will not be able to load those classes. So referring to classes without declaring it as dependent module, may lead it issues in Java 9 on.

@robertvazan
Copy link
Author

And why not declare the dependency on the servlet API? The javax.servlet-api package only contains various interfaces with no reference to anything heavy like servlet container.

Anyways, if adding server API dependency is not acceptable, the second best option is to publish a small extension library that only contains this functionality, so that people can add it if they need it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants