/*
* Copyright 2008-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nominanuda.springmvc;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.cookie.Cookie;
import org.apache.http.cookie.CookieOrigin;
import org.apache.http.cookie.CookieSpec;
import org.apache.http.cookie.MalformedCookieException;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.impl.cookie.DefaultCookieSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nominanuda.zen.common.Check;
public class HttpContext {
private final static String HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
private final static String HEADER_X_FORWARDED_URL = "X-Forwarded-URL";
private final static String COOKIE_DOMAIN = "*"; // TODO settable via spring?
private final ThreadLocal<Boolean> initialized = new ThreadLocal<Boolean>() {
protected Boolean initialValue() {
return false;
};
};
private final ThreadLocal<Map<String, Cookie>> requestCookies = new ThreadLocal<Map<String, Cookie>>() {
protected Map<String, Cookie> initialValue() {
return new HashMap<String, Cookie>();
}
};
private final ThreadLocal<Map<String, Cookie>> responseCookies = new ThreadLocal<Map<String, Cookie>>() {
protected Map<String, Cookie> initialValue() {
return new HashMap<String, Cookie>();
}
};
private final ThreadLocal<String> clientIp = new ThreadLocal<String>();
private final ThreadLocal<String> contextUrl = new ThreadLocal<String>();
private final ThreadLocal<String> contextPath = new ThreadLocal<String>();
private final ThreadLocal<String> contextRequest = new ThreadLocal<String>();
private final ThreadLocal<Long> contextTsStart = new ThreadLocal<Long>();
private final ThreadLocal<Long> contextTsCurrent = new ThreadLocal<Long>();
private final ThreadLocal<Long> contextMsProcess = new ThreadLocal<Long>();
private final ThreadLocal<Long> contextMsNetwork = new ThreadLocal<Long>();
private final Logger log = LoggerFactory.getLogger(HttpContext.class);
private final CookieSpec cookieSpec = new DefaultCookieSpec();
private Set<String> forwardedCookies = new HashSet<>();
private boolean verbose = false;
private static HttpContext THIS = new HttpContext();
public static synchronized HttpContext getInstance() {
return THIS;
}
/* request - client cookies -> thread local */
public void init(HttpServletRequest request) {
resetTimers();
if (request != null) {
String p = request.getPathInfo();
final int pathLength = (p == null ? 0 : p.length());
final StringBuffer urlSb = request.getRequestURL();
String simpleUrl = urlSb.toString(); // without qs
String qs = request.getQueryString();
if (qs != null) urlSb.append("?").append(qs);
final String fullUrl = urlSb.toString();
contextUrl.set(fullUrl);
requestCookies.remove();
responseCookies.remove();
StringBuilder cookiesDump = new StringBuilder();
Map<String, Cookie> cookiez = requestCookies.get();
for (javax.servlet.http.Cookie c : Check.ifNull(request.getCookies(), new javax.servlet.http.Cookie[0])) {
cookiez.put(c.getName(), new BasicClientCookie(c.getName(), c.getValue()));
cookiesDump.append(c.toString()).append(";");
}
String xForwardedFor = request.getHeader(HEADER_X_FORWARDED_FOR);
clientIp.set(xForwardedFor != null ? xForwardedFor : request.getRemoteAddr());
String xForwardedUrl = request.getHeader(HEADER_X_FORWARDED_URL);
if (xForwardedUrl != null) {
int i = xForwardedUrl.lastIndexOf("?");
simpleUrl = (i == -1 ? xForwardedUrl : xForwardedUrl.substring(0, i));
}
try {
simpleUrl = URLDecoder.decode(simpleUrl, "UTF-8"); // convert "%xx" stuff before working with lengths
} catch (UnsupportedEncodingException e) {
log.error(e.toString());
}
contextPath.set(simpleUrl.substring(0, simpleUrl.length() - pathLength));
if (verbose) log.info(">>> Time 0 [" + fullUrl + "]. cookies from client: " + cookiesDump.toString());
}
initialized.set(true);
}
/* request - thread local cookies -> other webapp */
public void writeTo(HttpRequest request) {
if (!initialized.get()) { // to avoid npe
init(null);
}
long time = System.currentTimeMillis();
long processTime = time - contextTsCurrent.get();
contextMsProcess.set(contextMsProcess.get() + processTime);
contextTsCurrent.set(time);
final String uri = request.getRequestLine().getUri();
contextRequest.set(uri);
List<Cookie> cookiez = new ArrayList<>();
StringBuilder cookiesDump = new StringBuilder();
for (String name : forwardedCookies) {
Cookie c = requestCookies.get().get(name);
if (c != null) {
cookiez.add(c);
cookiesDump.append(c.toString()).append(";");
}
}
if (!cookiez.isEmpty()) { // ...or formatCookies() throws error
for (Header h : cookieSpec.formatCookies(cookiez)) {
request.addHeader(h);
}
}
request.setHeader(HEADER_X_FORWARDED_FOR, clientIp.get());
if (verbose) log.info(" Time " + String.valueOf(time - contextTsStart.get()) + " (+" + processTime + "p) >>> [" + contextUrl.get() + "] >>> " + uri + ". thread local cookies " + cookiesDump.toString());
}
/* response - other webapp set-cookies -> thread local */
public void update(HttpResponse response) {
long time = System.currentTimeMillis();
long networkTime = time - contextTsCurrent.get();
contextMsNetwork.set(contextMsNetwork.get() + networkTime);
contextTsCurrent.set(time);
StringBuilder cookiesDump = new StringBuilder();
for (Header h : response.getAllHeaders()) {
if ("Set-Cookie".equalsIgnoreCase(h.getName())) {
try {
Map<String, Cookie> cookiez = getServerCookiesFrom(h);
requestCookies.get().putAll(cookiez);
responseCookies.get().putAll(cookiez);
cookiesDump.append(h.toString()).append(";");
} catch (MalformedCookieException e) {
log.error("discarding malformed cookie", e);
}
}
}
if (verbose) log.info(" Time " + String.valueOf(time - contextTsStart.get()) + " (+" + networkTime + "n) <<< [" + contextUrl.get() + "] <<< " + contextRequest.get() + ". Set-Cookie to thread local: " + cookiesDump.toString());
}
/* response - thread local set-cookies -> client */
public void writeTo(HttpServletResponse response) {
long time = System.currentTimeMillis();
long processTime = time - contextTsCurrent.get();
long processTimeTotal = contextMsProcess.get() + processTime;
long networkTimeTotal = contextMsNetwork.get();
StringBuilder cookiesDump = new StringBuilder();
for (Entry<String, Cookie> entry : responseCookies.get().entrySet()) {
javax.servlet.http.Cookie c = buildServletCookie(entry.getKey(), entry.getValue());
response.addCookie(c);
cookiesDump.append(c.toString()).append(";");
}
if (verbose) log.info("<<< Time total " + String.valueOf(time - contextTsStart.get()) + " (+" + processTime + "p " + processTimeTotal + "p " + networkTimeTotal + "n)" + " [" + contextUrl.get() + "]" + ". Set-Cookie to client: " + cookiesDump.toString());
}
/* access thread locals */
public String getCookie(String name) {
Cookie c = requestCookies.get().get(name);
return c == null ? null : c.getValue();
}
public void setCookieUntil(String name, String value, Long datetime) {
BasicClientCookie c = new BasicClientCookie(name, value);
c.setExpiryDate(datetime != null ? new Date(datetime) : null);
requestCookies.get().put(name, c);
responseCookies.get().put(name, c);
}
public void setCookieFor(String name, String value, long duration) {
Check.illegalargument.assertFalse(duration == 0, "use setCookie(...) instead of duration == 0");
Check.illegalargument.assertFalse(duration < 0, "use resetCookie(...) instead of duration < 0");
setCookieUntil(name, value, System.currentTimeMillis() + duration);
}
public void setCookie(String name, String value) {
setCookieUntil(name, value, null);
}
public void resetCookie(String name) {
if (requestCookies.get().remove(name) != null) {
responseCookies.get().put(name, null);
}
}
public String getClientIp() {
return clientIp.get();
}
public String getServletPath() {
return contextPath.get();
}
/* helpers */
private Map<String, Cookie> getServerCookiesFrom(Header h) throws MalformedCookieException {
CookieOrigin origin = new CookieOrigin(COOKIE_DOMAIN, 80, "/", true);
Map<String, Cookie> m = new HashMap<String, Cookie>();
for (Cookie c : cookieSpec.parse(h, origin)) {
m.put(c.getName(), c);
}
return m;
}
private javax.servlet.http.Cookie buildServletCookie(String name, Cookie cookie) {
javax.servlet.http.Cookie c = new javax.servlet.http.Cookie(name, "");
if (cookie == null) {
c.setMaxAge(0); // delete
} else {
c.setValue(cookie.getValue());
c.setComment(cookie.getComment());
c.setVersion(cookie.getVersion());
c.setSecure(cookie.isSecure());
// c.setDomain(""/*cookie.getDomain()*/);
Date d = cookie.getExpiryDate();
c.setMaxAge(d != null ? (int)((d.getTime() - System.currentTimeMillis()) / 1000) : -1);
}
c.setPath("/"/* cookie.getPath() */);
return c;
}
private void resetTimers() {
long time = System.currentTimeMillis();
contextTsStart.set(time);
contextTsCurrent.set(time);
contextMsProcess.set(0l);
contextMsNetwork.set(0l);
}
/* setters */
public void setForwardedCookies(Set<String> forwardedCookies) {
this.forwardedCookies = forwardedCookies;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
}