summaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
blob: dbe2b2ad5d525ebcbeff3bbf675c512fd6c81992 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright Yahoo. 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 com.yahoo.jdisc.http.HttpRequest.Method;
import org.eclipse.jetty.server.Request;

import javax.servlet.ServletException;
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.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.yahoo.jdisc.http.server.jetty.RequestUtils.getConnector;

/**
 * @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;

    private static final Set<String> servletSupportedMethods =
            Stream.of(Method.OPTIONS, Method.GET, Method.HEAD, Method.POST, Method.PUT, Method.DELETE, Method.TRACE)
                  .map(Method::name)
                  .collect(Collectors.toSet());

    public JDiscHttpServlet(JDiscContext context) {
        this.context = context;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doHead(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    /**
     * 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.)
     */
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        request.setAttribute(JDiscServerConnector.REQUEST_ATTRIBUTE, getConnector((Request) request));

        Metric.Context metricContext = getMetricContext(request);
        context.metric.add(MetricDefinitions.NUM_REQUESTS, 1, metricContext);
        context.metric.add(MetricDefinitions.JDISC_HTTP_REQUESTS, 1, metricContext);

        String method = request.getMethod().toUpperCase();
        if (servletSupportedMethods.contains(method)) {
            super.service(request, response);
        } else if (method.equals(Method.PATCH.name())) {
            // PATCH method is not handled by the Servlet spec
            dispatchHttpRequest(request, response);
        } else {
            // Divergence from HTTP / Servlet spec: JDisc returns 405 for both unknown and known (but unsupported) methods.
            response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
        }
    }

    private void dispatchHttpRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        AccessLogEntry accessLogEntry = new AccessLogEntry();
        request.setAttribute(ATTRIBUTE_NAME_ACCESS_LOG_ENTRY, accessLogEntry);
        try {
            switch (request.getDispatcherType()) {
                case REQUEST:
                    new HttpRequestDispatch(context, accessLogEntry, getMetricContext(request), request, response).dispatchRequest();
                    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(HttpServletRequest request) {
        return JDiscServerConnector.fromRequest(request).createRequestMetricContext(request, Map.of());
    }

    private static String formatAttributes(final HttpServletRequest request) {
        StringBuilder out = new StringBuilder();
        out.append("attributes = {");
        for (Enumeration<String> 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();
    }
}