All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.grizzly.servlet.ServletHandler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.grizzly.servlet;

import static jakarta.servlet.DispatcherType.REQUEST;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.Note;
import org.glassfish.grizzly.http.server.AfterServiceListener;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.server.SessionManager;
import org.glassfish.grizzly.http.server.util.ClassLoaderUtil;
import org.glassfish.grizzly.http.server.util.DispatcherHelper;
import org.glassfish.grizzly.http.server.util.Globals;
import org.glassfish.grizzly.http.server.util.HtmlHelper;
import org.glassfish.grizzly.http.server.util.MappingData;
import org.glassfish.grizzly.http.util.CharChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HttpRequestURIDecoder;
import org.glassfish.grizzly.http.util.HttpStatus;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;

/**
 * HttpHandler implementation that provides an entry point for processing a Servlet request.
 *
 * @author Jeanfrancois Arcand
 */
public class ServletHandler extends HttpHandler {

    private static final Logger LOGGER = Grizzly.logger(ServletHandler.class);

    static final Note SERVLET_REQUEST_NOTE = Request.createNote(HttpServletRequestImpl.class.getName());
    static final Note SERVLET_RESPONSE_NOTE = Request.createNote(HttpServletResponseImpl.class.getName());

    static final ServletAfterServiceListener servletAfterServiceListener = new ServletAfterServiceListener();

    protected String servletClassName;
    protected Class servletClass;
    protected volatile Servlet servletInstance = null;
    private String contextPath = "";
    private final Object lock = new Object();

    /**
     * The {@link WebappContext}
     */
    private final WebappContext servletCtx;
    /**
     * The {@link ServletConfigImpl}
     */
    private final ServletConfigImpl servletConfig;
    /**
     * The {@link SessionManager}
     */
    private SessionManager sessionManager = ServletSessionManager.instance();
    /**
     * Holder for our configured properties.
     */
    protected final Map properties = new HashMap<>();
    /**
     * Initialize the {@link ServletContext}
     */
    protected boolean initialize = true;
    protected ClassLoader classLoader;

    protected ExpectationHandler expectationHandler;

    protected FilterChainFactory filterChainFactory;

    // Listeners to be invoked when ServletHandler.destroy is called
    private List onDestroyListeners;

    // ------------------------------------------------------------ Constructors

    protected ServletHandler(final ServletConfigImpl servletConfig) {
        this.servletConfig = servletConfig;
        servletCtx = (WebappContext) servletConfig.getServletContext();
    }

    // ---------------------------------------------------------- Public Methods

    /**
     * {@inheritDoc}
     */
    @Override
    public void start() {
        try {
            configureServletEnv();
        } catch (Throwable t) {
            LOGGER.log(Level.SEVERE, "start", t);
        }
    }

    /**
     * Override parent's
     * {@link HttpHandler#sendAcknowledgment(org.glassfish.grizzly.http.server.Request, org.glassfish.grizzly.http.server.Response)}
     * to let {@link ExpectationHandler} (if one is registered) process the expectation.
     */
    @Override
    protected boolean sendAcknowledgment(Request request, Response response) throws IOException {
        return expectationHandler != null || super.sendAcknowledgment(request, response);

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void service(Request request, Response response) throws Exception {
        if (classLoader != null) {
            final ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);
            try {
                doServletService(request, response);
            } finally {
                Thread.currentThread().setContextClassLoader(prevClassLoader);
            }
        } else {
            doServletService(request, response);
        }
    }

    protected void doServletService(final Request request, final Response response) {
        try {
            final String uri = request.getRequestURI();

            // The request is not for us.
            if (contextPath.length() > 0 && !uri.startsWith(contextPath)) {
                customizeErrorPage(response, "Resource Not Found", 404, null);
                return;
            }

            final HttpServletRequestImpl servletRequest = HttpServletRequestImpl.create();
            final HttpServletResponseImpl servletResponse = HttpServletResponseImpl.create();

            setPathData(request, servletRequest);
            servletRequest.initialize(request, servletResponse, servletCtx);
            servletResponse.initialize(response, servletRequest);

            request.setNote(SERVLET_REQUEST_NOTE, servletRequest);
            request.setNote(SERVLET_RESPONSE_NOTE, servletResponse);

            request.addAfterServiceListener(servletAfterServiceListener);

            loadServlet();

            setDispatcherPath(request, getCombinedPath(servletRequest));

            final String serverInfo = servletCtx.getServerInfo();
            if (serverInfo != null && !serverInfo.isEmpty()) {
                servletResponse.addHeader(Header.Server.toString(), serverInfo);
            }

            if (expectationHandler != null) {
                final AckActionImpl ackAction = new AckActionImpl(response);
                expectationHandler.onExpectAcknowledgement(servletRequest, servletResponse, ackAction);
                if (!ackAction.isAcknowledged()) {
                    ackAction.acknowledge();
                } else if (ackAction.isFailAcknowledgement()) {
                    return;
                }
            }

            FilterChainInvoker filterChain = getFilterChain(request);
            if (filterChain != null) {
                filterChain.invokeFilterChain(servletRequest, servletResponse);
            } else {
                servletInstance.service(servletRequest, servletResponse);
            }

            // Request may want to initialize async processing
            servletRequest.onAfterService();
        } catch (Throwable ex) {
            LOGGER.log(Level.SEVERE, "service exception:", ex);
            customizeErrorPage(response, "Internal Error", 500, ex);
        }
    }

    protected FilterChainInvoker getFilterChain(Request request) {
        if (filterChainFactory != null) {
            return filterChainFactory.createFilterChain(request, servletInstance, REQUEST);
        }
        return null;
    }

    private void setDispatcherPath(final Request request, final String path) {
        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, path);
    }

    /**
     * Combines the servletPath and the pathInfo.
     *
     * If pathInfo is null, it is ignored. If servletPath is null, then null is
     * returned.
     *
     * @return The combined path with pathInfo appended to servletInfo
     */
    private String getCombinedPath(final HttpServletRequest request) {
        if (request.getServletPath() == null) {
            return null;
        }
        if (request.getPathInfo() == null) {
            return request.getServletPath();
        }
        return request.getServletPath() + request.getPathInfo();
    }

    protected void setPathData(final Request from, final HttpServletRequestImpl to) {

        final MappingData data = from.obtainMappingData();
        to.setServletPath(data.wrapperPath.toString());
        to.setPathInfo(data.pathInfo.toString());
        to.setContextPath(data.contextPath.toString());
    }

    void doServletService(final ServletRequest servletRequest, final ServletResponse servletResponse, final DispatcherType dispatcherType)
            throws IOException, ServletException {
        try {
            loadServlet();
            FilterChainImpl filterChain = filterChainFactory.createFilterChain(servletRequest, servletInstance, dispatcherType);
            if (filterChain != null) {
                filterChain.invokeFilterChain(servletRequest, servletResponse);
            } else {
                servletInstance.service(servletRequest, servletResponse);
            }
        } catch (ServletException se) {
            LOGGER.log(Level.SEVERE, "service exception:", se);
            throw se;
        } catch (IOException ie) {
            LOGGER.log(Level.SEVERE, "service exception:", ie);
            throw ie;
        }
    }

    /**
     * Customize the error page returned to the client.
     * 
     * @param response the {@link Response}
     * @param message the HTTP error message
     * @param errorCode the error code.
     */
    public void customizeErrorPage(final Response response, final String message, final int errorCode, final Throwable t) {
        if (!response.isCommitted()) {
            try {
                HtmlHelper.setErrorAndSendErrorPage(response.getRequest(), response, response.getErrorPageGenerator(), errorCode, message, message, t);
            } catch (IOException ex) {
                // We are in a very bad shape. Ignore.
            }
        }
    }

    /**
     * Load a {@link Servlet} instance.
     *
     * @throws jakarta.servlet.ServletException If failed to {@link Servlet#init(jakarta.servlet.ServletConfig)}.
     */
    protected void loadServlet() throws ServletException {

        if (servletInstance == null) {
            synchronized (lock) {
                if (servletInstance == null) {
                    Servlet newServletInstance;
                    if (servletClassName != null) {
                        newServletInstance = (Servlet) ClassLoaderUtil.load(servletClassName);
                    } else {
                        try {
                            newServletInstance = servletClass.newInstance();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                    LOGGER.log(Level.INFO, "Loading Servlet: {0}", newServletInstance.getClass().getName());
                    newServletInstance.init(servletConfig);
                    servletInstance = newServletInstance;
                }
            }
        }

    }

    /**
     * Configure the {@link WebappContext} and {@link ServletConfigImpl}
     *
     * @throws jakarta.servlet.ServletException Error while configuring {@link Servlet}.
     */
    protected void configureServletEnv() throws ServletException {
        if (contextPath.length() > 0) {
            final CharChunk cc = new CharChunk();
            char[] ch = contextPath.toCharArray();
            cc.setChars(ch, 0, ch.length);
            HttpRequestURIDecoder.normalizeChars(cc);
            contextPath = cc.toString();
        }

        if ("".equals(contextPath)) {
            contextPath = "";
        }

    }

    /**
     * Return the {@link Servlet} instance used by this {@link ServletHandler}
     * 
     * @return {@link Servlet} instance.
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    public Servlet getServletInstance() {
        return servletInstance;
    }

    /**
     * Set the {@link Servlet} instance used by this {@link ServletHandler}
     * 
     * @param servletInstance an instance of Servlet.
     */
    protected void setServletInstance(Servlet servletInstance) {
        this.servletInstance = servletInstance;
    }

    protected void setServletClassName(final String servletClassName) {
        this.servletClassName = servletClassName;
    }

    protected void setServletClass(final Class servletClass) {
        this.servletClass = servletClass;
    }

    /**
     * Set the {@link SessionManager} instance used by this {@link ServletHandler}
     * 
     * @param sessionManager an implementation of SessionManager.
     */
    protected void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    /**
     *
     * Returns the portion of the request URI that indicates the context of the request. The context path always comes first
     * in a request URI. The path starts with a "/" character but does not end with a "/" character. For servlets in the
     * default (root) context, this method returns "". The container does not decode this string.
     *
     * 

* It is possible that a servlet container may match a context by more than one context path. In such cases this method * will return the actual context path used by the request and it may differ from the path returned by the * {@link jakarta.servlet.ServletContext#getContextPath()} method. The context path returned by * {@link jakarta.servlet.ServletContext#getContextPath()} should be considered as the prime or preferred context path * of the application. * * @return a String specifying the portion of the request URI that indicates the context of the request * * @see jakarta.servlet.ServletContext#getContextPath() */ public String getContextPath() { return contextPath; } /** * Programmatically set the context path of the Servlet. * * @param contextPath Context path. */ public void setContextPath(String contextPath) { this.contextPath = contextPath; } /** * Destroy this Servlet and its associated {@link jakarta.servlet.ServletContextListener} */ @Override public void destroy() { try { if (classLoader != null) { ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { super.destroy(); if (servletInstance != null) { servletInstance.destroy(); servletInstance = null; } } finally { Thread.currentThread().setContextClassLoader(prevClassLoader); } } else { super.destroy(); } } finally { // notify destroy listeners (if any) if (onDestroyListeners != null) { for (int i = 0; i < onDestroyListeners.size(); i++) { try { onDestroyListeners.get(i).run(); } catch (Throwable t) { LOGGER.log(Level.WARNING, "onDestroyListener error", t); } } onDestroyListeners = null; } } } protected WebappContext getServletCtx() { return servletCtx; } public ClassLoader getClassLoader() { return classLoader; } public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } public ServletConfigImpl getServletConfig() { return servletConfig; } @Override public String getName() { return servletConfig.getServletName(); } /** * Get the {@link ExpectationHandler} responsible for processing Expect: header (for example "Expect: * 100-Continue"). * * @return the {@link ExpectationHandler} responsible for processing Expect: header (for example "Expect: * 100-Continue"). */ public ExpectationHandler getExpectationHandler() { return expectationHandler; } /** * Set the {@link ExpectationHandler} responsible for processing Expect: header (for example "Expect: * 100-Continue"). * * @param expectationHandler the {@link ExpectationHandler} responsible for processing Expect: header (for * example "Expect: 100-Continue"). */ public void setExpectationHandler(ExpectationHandler expectationHandler) { this.expectationHandler = expectationHandler; } @Override protected void setDispatcherHelper(final DispatcherHelper dispatcherHelper) { servletCtx.setDispatcherHelper(dispatcherHelper); } protected void setFilterChainFactory(final FilterChainFactory filterChainFactory) { this.filterChainFactory = filterChainFactory; } /** * Overrides default (JSESSIONID) session cookie name. * * @return the session cookie name */ @Override protected String getSessionCookieName() { return servletCtx.getSessionCookieConfig().getName(); } /** * @return Servlet-aware {@link SessionManager} */ @Override protected SessionManager getSessionManager(Request request) { final SessionManager sm = request.getHttpFilter().getConfiguration().getSessionManager(); return sm != null ? sm : this.sessionManager; } /** * Adds the {@link Runnable}, which will be invoked when {@link #destroy()} is called. * * @param r {@link Runnable}, which contains destroy listener logic. */ void addOnDestroyListener(final Runnable r) { if (onDestroyListeners == null) { onDestroyListeners = new ArrayList<>(2); } onDestroyListeners.add(r); } // ---------------------------------------------------------- Nested Classes /** * AfterServiceListener, which is responsible for recycle servlet request and response objects. */ static final class ServletAfterServiceListener implements AfterServiceListener { @Override public void onAfterService(final Request request) { final HttpServletRequestImpl servletRequest = getServletRequest(request); final HttpServletResponseImpl servletResponse = getServletResponse(request); if (servletRequest != null) { servletRequest.recycle(); servletResponse.recycle(); } } } static final class AckActionImpl implements ExpectationHandler.AckAction { private boolean isAcknowledged; private boolean isFailAcknowledgement; private final Response response; private AckActionImpl(final Response response) { this.response = response; } @Override public void acknowledge() throws IOException { if (isAcknowledged) { throw new IllegalStateException("Already acknowledged"); } isAcknowledged = true; response.setStatus(HttpStatus.CONINTUE_100); response.sendAcknowledgement(); } @Override public void fail() throws IOException { if (isAcknowledged) { throw new IllegalStateException("Already acknowledged"); } isAcknowledged = true; isFailAcknowledgement = true; response.setStatus(HttpStatus.EXPECTATION_FAILED_417); response.finish(); } public boolean isAcknowledged() { return isAcknowledged; } public boolean isFailAcknowledgement() { return isFailAcknowledgement; } } static HttpServletRequestImpl getServletRequest(final Request request) { return request.getNote(SERVLET_REQUEST_NOTE); } static HttpServletResponseImpl getServletResponse(final Request request) { return request.getNote(SERVLET_RESPONSE_NOTE); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy