// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.server.jetty; import com.yahoo.container.logging.AccessLogEntry; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.handler.OverloadException; import org.eclipse.jetty.server.HttpConnection; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import static com.yahoo.jdisc.http.server.jetty.ConnectorFactory.JDiscServerConnector; /** * @author Simon Thoresen Hult * @author bjorncs */ @WebServlet(asyncSupported = true, description = "Bridge between Servlet and JDisc APIs") class JDiscHttpServlet extends HttpServlet { public static final String ATTRIBUTE_NAME_ACCESS_LOG_ENTRY = JDiscHttpServlet.class.getName() + "_access-log-entry"; private final static Logger log = Logger.getLogger(JDiscHttpServlet.class.getName()); private final JDiscContext context; public JDiscHttpServlet(JDiscContext context) { this.context = context; } @Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } @Override protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } @Override protected void doHead(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } @Override protected void doPut(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } @Override protected void doDelete(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } @Override protected void doOptions(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } @Override protected void doTrace(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { dispatchHttpRequest(request, response); } private static final Set JETTY_UNSUPPORTED_METHODS = new HashSet<>(Arrays.asList( "PATCH")); /** * Override to set connector attribute before the request becomes an upgrade request in the web socket case. * (After the upgrade, the HttpConnection is no longer available.) */ @SuppressWarnings("deprecation") @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute(JDiscServerConnector.REQUEST_ATTRIBUTE, getConnector(request)); Metric.Context metricContext = getMetricContext(request); context.metric.add(JettyHttpServer.Metrics.NUM_REQUESTS, 1, metricContext); context.metric.add(JettyHttpServer.Metrics.JDISC_HTTP_REQUESTS, 1, metricContext); context.metric.add(JettyHttpServer.Metrics.MANHATTAN_NUM_REQUESTS, 1, metricContext); if (JETTY_UNSUPPORTED_METHODS.contains(request.getMethod().toUpperCase())) { dispatchHttpRequest(request, response); } else { super.service(request, response); } } static HttpConnection getConnection(HttpServletRequest request) { return (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection"); } static JDiscServerConnector getConnector(HttpServletRequest request) { return (JDiscServerConnector)getConnection(request).getConnector(); } private void dispatchHttpRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException { final AccessLogEntry accessLogEntry = new AccessLogEntry(); AccessLogRequestLog.populateAccessLogEntryFromHttpServletRequest(request, accessLogEntry); request.setAttribute(ATTRIBUTE_NAME_ACCESS_LOG_ENTRY, accessLogEntry); try { switch (request.getDispatcherType()) { case REQUEST: new HttpRequestDispatch(context, accessLogEntry, getMetricContext(request), request, response).dispatch(); break; default: if (log.isLoggable(Level.INFO)) { log.info("Unexpected " + request.getDispatcherType() + "; " + formatAttributes(request)); } break; } } catch (OverloadException e) { // nop } catch (RuntimeException e) { throw new ExceptionWrapper(e); } } private static Metric.Context getMetricContext(ServletRequest request) { return JDiscServerConnector.fromRequest(request) .getMetricContext(); } private static String formatAttributes(final HttpServletRequest request) { final StringBuilder out = new StringBuilder(); out.append("attributes = {"); for (Enumeration names = request.getAttributeNames(); names.hasMoreElements(); ) { String name = names.nextElement(); out.append(" '").append(name).append("' = '").append(request.getAttribute(name)).append("'"); if (names.hasMoreElements()) { out.append(","); } } out.append(" }"); return out.toString(); } }