package io.cattle.platform.api.servlet; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.metrics.util.MetricsUtil; import io.cattle.platform.spring.web.SpringFilter; import io.cattle.platform.util.exception.ExceptionUtils; import io.github.ibuildthecloud.gdapi.context.ApiContext; import io.github.ibuildthecloud.gdapi.request.ApiRequest; import io.github.ibuildthecloud.gdapi.servlet.ApiRequestFilterDelegate; import io.github.ibuildthecloud.gdapi.util.RequestUtils; import io.github.ibuildthecloud.gdapi.version.Versions; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.commons.lang3.StringUtils; import com.codahale.metrics.Timer; import com.netflix.config.DynamicStringListProperty; import com.netflix.config.DynamicStringProperty; public class ApiRequestFilter extends SpringFilter { private static final DynamicStringListProperty IGNORE = ArchaiusUtil.getList("api.ignore.paths"); private static final DynamicStringProperty PL_SETTING = ArchaiusUtil.getString("ui.pl"); private static final String PL = "PL"; private static final String LANG = "LANG"; private static final String VERSION = "X-Rancher-Version"; private static final DynamicStringProperty LOCALIZATION = ArchaiusUtil.getString("localization"); private static final DynamicStringProperty SERVER_VERSION = ArchaiusUtil.getString("rancher.server.version"); ApiRequestFilterDelegate delegate; Versions versions; Map<String, Timer> timers = new ConcurrentHashMap<String, Timer>(); IndexFile indexFile = new IndexFile(); @Override public void init(FilterConfig filterConfig) throws ServletException { super.init(filterConfig); indexFile.init(); } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest)request; String path = httpRequest.getServletPath(); boolean ignore = false; for (String prefix : IGNORE.get()) { if (path.startsWith(prefix)) { ignore = true; break; } } if (ignore) { chain.doFilter(request, response); return; } addPLCookie(httpRequest, (HttpServletResponse) response); addDefaultLanguageCookie(httpRequest, (HttpServletResponse) response); addVersionHeader(httpRequest, (HttpServletResponse) response); if (isUIRequest(httpRequest, path)) { if (path.contains(".") || !indexFile.canServeContent()) { chain.doFilter(request, response); return; } else { indexFile.serveIndex((HttpServletRequest)request, (HttpServletResponse) response); return; } } try { new ManagedContextRunnable() { @Override protected void runInContext() { long start = System.currentTimeMillis(); boolean success = false; ApiContext context = null; try { context = delegate.doFilter(request, response, chain); success = true; } catch (IOException e) { throw new WrappedException(e); } catch (ServletException e) { throw new WrappedException(e); } finally { done(context, start, success); } } }.run(); } catch (WrappedException e) { Throwable t = e.getCause(); ExceptionUtils.rethrow(t, IOException.class); ExceptionUtils.rethrow(t, ServletException.class); ExceptionUtils.rethrowExpectedRuntime(t); } } protected void addVersionHeader(HttpServletRequest httpRequest, HttpServletResponse response) { response.setHeader(VERSION, SERVER_VERSION.get()); } protected void done(ApiContext context, long start, boolean success) { if (context == null) { return; } ApiRequest request = context.getApiRequest(); if (request == null) { return; } if (request.getResponseCode() >= 400) { success = false; } long duration = System.currentTimeMillis() - start; if (request != null) { String key = String.format("api.%s.%s.%s", success ? "success" : "failed", request.getType(), request.getMethod().toLowerCase()); Timer timer = timers.get(key); if (timer == null) { timer = MetricsUtil.getRegistry().timer(key); timers.put(key, timer); } timer.update(duration, TimeUnit.MILLISECONDS); } } protected boolean isUIRequest(HttpServletRequest request, String path) { path = path.replaceAll("//+", "/"); if ("/".equals(path)) { return RequestUtils.isBrowser(request, false); } boolean found = false; for (String version : versions.getVersions()) { if (path.startsWith("/" + version)) { found = true; break; } } return !found; } public ApiRequestFilterDelegate getDelegate() { return delegate; } @Inject public void setDelegate(ApiRequestFilterDelegate delegate) { this.delegate = delegate; } public Versions getVersions() { return versions; } @Inject public void setVersions(Versions versions) { this.versions = versions; } private static final class WrappedException extends RuntimeException { private static final long serialVersionUID = 8188803805854482331L; public WrappedException(Throwable cause) { super(cause); } } private void addPLCookie(HttpServletRequest httpRequest, HttpServletResponse response) { Cookie plCookie = null; if (httpRequest.getCookies() != null) { for (Cookie c : httpRequest.getCookies()) { if (PL.equals(c.getName()) && c.getName() != null) { plCookie = c; break; } } } if (plCookie == null || !PL_SETTING.getValue().equalsIgnoreCase(plCookie.getValue())) { plCookie = new Cookie(PL, PL_SETTING.getValue()); plCookie.setPath("/"); response.addCookie(plCookie); } } private void addDefaultLanguageCookie(HttpServletRequest httpRequest, HttpServletResponse response) { Cookie languageCookie = null; if(!StringUtils.isNotBlank(LOCALIZATION.get())) return; if(httpRequest.getCookies()!=null) { for(Cookie c : httpRequest.getCookies()) { if(LANG.equals(c.getName()) && c.getName()!=null) { languageCookie = c; break; } } } if(languageCookie == null) { languageCookie = new Cookie(LANG, LOCALIZATION.get()); languageCookie.setPath("/"); response.addCookie(languageCookie); } } }