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

org.eclipse.jetty.server.Server Maven / Gradle / Ivy

There is a newer version: 11.0.0.beta1
Show newest version
// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================

package org.eclipse.jetty.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.Container;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadPool;

/* ------------------------------------------------------------ */
/** Jetty HTTP Servlet Server.
 * This class is the main class for the Jetty HTTP Servlet server.
 * It aggregates Connectors (HTTP request receivers) and request Handlers.
 * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
 * to run jobs that will eventually call the handle method.
 *
 *  @org.apache.xbean.XBean  description="Creates an embedded Jetty web server"
 */
public class Server extends HandlerWrapper implements Attributes
{
    private static final String _version;
    static
    {
        if (Server.class.getPackage()!=null && Server.class.getPackage().getImplementationVersion()!=null)
            _version=Server.class.getPackage().getImplementationVersion();
        else
            _version=System.getProperty("jetty.version","7.x.y-SNAPSHOT");
    }
    private final Container _container=new Container();
    private final AttributesMap _attributes = new AttributesMap();
    private final List _dependentBeans=new ArrayList();
    private ThreadPool _threadPool;
    private Connector[] _connectors;
    private SessionIdManager _sessionIdManager;
    private boolean _sendServerVersion = true; //send Server: header
    private boolean _sendDateHeader = false; //send Date: header
    private int _graceful=0;
    private boolean _stopAtShutdown;
    private int _maxCookieVersion=1;
    

    /* ------------------------------------------------------------ */
    public Server()
    {
        setServer(this); 
    }
    
    /* ------------------------------------------------------------ */
    /** Convenience constructor
     * Creates server and a {@link SelectChannelConnector} at the passed port.
     */
    public Server(int port)
    {
        setServer(this);

        Connector connector=new SelectChannelConnector();
        connector.setPort(port);
        setConnectors(new Connector[]{connector});
    }
    
    /* ------------------------------------------------------------ */
    /** Convenience constructor
     * Creates server and a {@link SelectChannelConnector} at the passed address.
     */
    public Server(InetSocketAddress addr)
    {
        setServer(this);

        Connector connector=new SelectChannelConnector();
        connector.setHost(addr.getHostName());
        connector.setPort(addr.getPort());
        setConnectors(new Connector[]{connector});
    }


    /* ------------------------------------------------------------ */
    public static String getVersion()
    {
        return _version;
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @return Returns the container.
     */
    public Container getContainer()
    {
        return _container;
    }

    /* ------------------------------------------------------------ */
    public boolean getStopAtShutdown()
    {
        return _stopAtShutdown;
    }
    
    /* ------------------------------------------------------------ */
    public void setStopAtShutdown(boolean stop)
    {
        _stopAtShutdown=stop;
        if (stop)
            ShutdownThread.register(this);
        else
            ShutdownThread.deregister(this);
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @return Returns the connectors.
     */
    public Connector[] getConnectors()
    {
        return _connectors;
    }

    /* ------------------------------------------------------------ */
    public void addConnector(Connector connector)
    {
        setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class));
    }

    /* ------------------------------------------------------------ */
    /**
     * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to 
     * remove a connector.
     * @param connector The connector to remove.
     */
    public void removeConnector(Connector connector) {
        setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector));
    }

    /* ------------------------------------------------------------ */
    /** Set the connectors for this server.
     * Each connector has this server set as it's ThreadPool and its Handler.
     * @param connectors The connectors to set.
     */
    public void setConnectors(Connector[] connectors)
    {
        if (connectors!=null)
        {
            for (int i=0;i itor = _dependentBeans.iterator();
        while (itor.hasNext())
        {   
            try
            {
                Object o=itor.next();
                if (o instanceof LifeCycle)
                    ((LifeCycle)o).start(); 
            }
            catch (Throwable e) {mex.add(e);}
        }
        
        if (_sessionIdManager!=null)
            _sessionIdManager.start();
        
        try
        {
            if (_threadPool instanceof LifeCycle)
                ((LifeCycle)_threadPool).start();
        } 
        catch(Throwable e) { mex.add(e);}
        
        try 
        { 
            super.doStart(); 
        } 
        catch(Throwable e) 
        { 
            Log.warn("Error starting handlers",e);
        }
        
        if (_connectors!=null)
        {
            for (int i=0;i<_connectors.length;i++)
            {
                try{_connectors[i].start();}
                catch(Throwable e)
                {
                    mex.add(e);
                }
            }
        }
        if (Log.isDebugEnabled())
            System.err.println(dump());
        
        mex.ifExceptionThrow();
    }

    /* ------------------------------------------------------------ */
    @Override
    protected void doStop() throws Exception
    {
        MultiException mex=new MultiException();
        
        if (_graceful>0)
        {
            if (_connectors!=null)
            {
                for (int i=_connectors.length;i-->0;)
                {
                    Log.info("Graceful shutdown {}",_connectors[i]);
                    try{_connectors[i].close();}catch(Throwable e){mex.add(e);}
                }
            }
            
            Handler[] contexts = getChildHandlersByClass(Graceful.class);
            for (int c=0;c0;)
                try{_connectors[i].stop();}catch(Throwable e){mex.add(e);}
        }

        try {super.doStop(); } catch(Throwable e) { mex.add(e);}
        
        if (_sessionIdManager!=null)
            _sessionIdManager.stop();
        
        try
        {
            if (_threadPool instanceof LifeCycle)
                ((LifeCycle)_threadPool).stop();
        }
        catch(Throwable e){mex.add(e);}
        
        if (!_dependentBeans.isEmpty())
        {
            ListIterator itor = _dependentBeans.listIterator(_dependentBeans.size());
            while (itor.hasPrevious())
            {
                try
                {
                    Object o =itor.previous();
                    if (o instanceof LifeCycle)
                        ((LifeCycle)o).stop(); 
                }
                catch (Throwable e) {mex.add(e);}
            }
        }
       
        mex.ifExceptionThrow();

        if (getStopAtShutdown())
            ShutdownThread.deregister(this);
    }

    /* ------------------------------------------------------------ */
    /* Handle a request from a connection.
     * Called to handle a request on the connection when either the header has been received,
     * or after the entire request has been received (for short requests of known length), or
     * on the dispatch of an async request.
     */
    public void handle(HttpConnection connection) throws IOException, ServletException
    {
        final String target=connection.getRequest().getPathInfo();
        final Request request=connection.getRequest();
        final Response response=connection.getResponse();
        
        if (Log.isDebugEnabled())
        {
            Log.debug("REQUEST "+target+" on "+connection);
            handle(target, request, request, response);
            Log.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
        }
        else
            handle(target, request, request, response);
    }
    
    /* ------------------------------------------------------------ */
    /* Handle a request from a connection.
     * Called to handle a request on the connection when either the header has been received,
     * or after the entire request has been received (for short requests of known length), or
     * on the dispatch of an async request.
     */
    public void handleAsync(HttpConnection connection) throws IOException, ServletException
    {
        final AsyncContinuation async = connection.getRequest().getAsyncContinuation();
        final AsyncContinuation.AsyncEventState state = async.getAsyncEventState();

        final Request baseRequest=connection.getRequest();
        final String path=state.getPath();

        if (path!=null)
        {
            // this is a dispatch with a path
            baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
            baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());

            baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,state.getSuspendedContext().getContextPath());

            final String contextPath=state.getServletContext().getContextPath();
            HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path));
            baseRequest.setUri(uri);
            baseRequest.setRequestURI(null);
            baseRequest.setPathInfo(baseRequest.getRequestURI());
            if (uri.getQuery()!=null)
                baseRequest.mergeQueryString(uri.getQuery());    
        }

        final String target=baseRequest.getPathInfo();
        final HttpServletRequest request=(HttpServletRequest)async.getRequest();
        final HttpServletResponse response=(HttpServletResponse)async.getResponse();

        if (Log.isDebugEnabled())
        {
            Log.debug("REQUEST "+target+" on "+connection);
            handle(target, baseRequest, request, response);
            Log.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
        }
        else
            handle(target, baseRequest, request, response);

    }
    
    

    /* ------------------------------------------------------------ */
    public void join() throws InterruptedException 
    {
        getThreadPool().join();
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * @return Returns the sessionIdManager.
     */
    public SessionIdManager getSessionIdManager()
    {
        return _sessionIdManager;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * @param sessionIdManager The sessionIdManager to set.
     */
    public void setSessionIdManager(SessionIdManager sessionIdManager)
    {
        _container.update(this,_sessionIdManager,sessionIdManager, "sessionIdManager",true);
        _sessionIdManager = sessionIdManager;
    }

    /* ------------------------------------------------------------ */
    public void setSendServerVersion (boolean sendServerVersion)
    {
        _sendServerVersion = sendServerVersion;
    }

    /* ------------------------------------------------------------ */
    public boolean getSendServerVersion()
    {
        return _sendServerVersion;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param sendDateHeader
     */
    public void setSendDateHeader(boolean sendDateHeader)
    {
        _sendDateHeader = sendDateHeader;
    }

    /* ------------------------------------------------------------ */
    public boolean getSendDateHeader()
    {
        return _sendDateHeader;
    }

    /* ------------------------------------------------------------ */
    /** Get the maximum cookie version.
     * @return the maximum set-cookie version sent by this server
     */
    public int getMaxCookieVersion()
    {
        return _maxCookieVersion;
    }

    /* ------------------------------------------------------------ */
    /** Set the maximum cookie version.
     * @param maxCookieVersion the maximum set-cookie version sent by this server
     */
    public void setMaxCookieVersion(int maxCookieVersion)
    {
        _maxCookieVersion = maxCookieVersion;
    }

    /* ------------------------------------------------------------ */
    /**
     * Add a LifeCycle object to be started/stopped
     * along with the Server.
     * @deprecated Use {@link #addBean(Object)}
     * @param c
     */
    @Deprecated
    public void addLifeCycle (LifeCycle c)
    {
        addBean(c);
    }

    /* ------------------------------------------------------------ */
    /**
     * Add an associated bean.
     * The bean will be added to the servers {@link Container}
     * and if it is a {@link LifeCycle} instance, it will be 
     * started/stopped along with the Server.
     * @param o the bean object to add
     */
    public void addBean(Object o)
    {
        if (o == null)
            return;
        
        if (!_dependentBeans.contains(o)) 
        {
            _dependentBeans.add(o);
            _container.addBean(o);
        }
        
        try
        {
            if (isStarted() && o instanceof LifeCycle)
                ((LifeCycle)o).start();
        }
        catch (Exception e)
        {
            throw new RuntimeException (e);
        }
    }

    /* ------------------------------------------------------------ */
    /** Get dependent beans of a specific class
     * @see #addBean(Object)
     * @param clazz
     * @return List of beans.
     */
    public  List getBeans(Class clazz)
    {
        ArrayList beans = new ArrayList();
        Iterator iter = _dependentBeans.iterator();
        while (iter.hasNext())
        {
            Object o = iter.next();
            if (clazz.isInstance(o))
                beans.add((T)o);
        }
        return beans;
    }
    
    /* ------------------------------------------------------------ */
    /** Get dependent bean of a specific class.
     * If more than one bean of the type exist, the first is returned.
     * @see #addBean(Object)
     * @param clazz
     * @return bean or null
     */
    public  T getBean(Class clazz)
    {
        Iterator iter = _dependentBeans.iterator();
        T t=null;
        int count=0;
        while (iter.hasNext())
        {
            Object o = iter.next();
            if (clazz.isInstance(o))
            {
                count++;
                if (t==null)
                    t=(T)o;
            }
        }
        if (count>1)
            Log.debug("getBean({}) 1 of {}",clazz.getName(),count);
        
        return t;
    }
    
    /**
     * Remove a LifeCycle object to be started/stopped 
     * along with the Server
     * @deprecated Use {@link #removeBean(Object)}
     */
    @Deprecated
    public void removeLifeCycle (LifeCycle c)
    {
        removeBean(c);
    }

    /**
     * Remove an associated bean.
     */
    public void removeBean (Object o)
    {
        if (o == null)
            return;
        _dependentBeans.remove(o);
        _container.removeBean(o);
    }

    /* ------------------------------------------------------------ */
    /* 
     * @see org.eclipse.util.AttributesMap#clearAttributes()
     */
    public void clearAttributes()
    {
        _attributes.clearAttributes();
    }

    /* ------------------------------------------------------------ */
    /* 
     * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
     */
    public Object getAttribute(String name)
    {
        return _attributes.getAttribute(name);
    }

    /* ------------------------------------------------------------ */
    /* 
     * @see org.eclipse.util.AttributesMap#getAttributeNames()
     */
    public Enumeration getAttributeNames()
    {
        return AttributesMap.getAttributeNamesCopy(_attributes);
    }

    /* ------------------------------------------------------------ */
    /* 
     * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
     */
    public void removeAttribute(String name)
    {
        _attributes.removeAttribute(name);
    }

    /* ------------------------------------------------------------ */
    /* 
     * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
     */
    public void setAttribute(String name, Object attribute)
    {
        _attributes.setAttribute(name, attribute);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return the graceful
     */
    public int getGracefulShutdown()
    {
        return _graceful;
    }

    /* ------------------------------------------------------------ */
    /**
     * Set graceful shutdown timeout.  If set, the internal doStop() method will not immediately stop the 
     * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted
     * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests
     * will be accepted, but existing requests can complete.  The server will then wait the configured timeout 
     * before stopping.
     * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server.
     * 
     */
    public void setGracefulShutdown(int timeoutMS)
    {
        _graceful=timeoutMS;
    }
    
    /* ------------------------------------------------------------ */
    @Override
    public String toString()
    {
        return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
    }

    
    /* ------------------------------------------------------------ */
    @Override
    protected void dump(Appendable out,String indent) throws IOException
    {
        out.append(toString()).append(isStarted()?" started":" STOPPED").append('\n');
        if (_connectors != null)
            for (Connector c : _connectors)
                out.append(" +-").append(String.valueOf(c)).append('\n');
        if (_threadPool != null)
            out.append(" +-").append(String.valueOf(_threadPool)).append('\n');
        dumpHandlers(out,indent);
    }


    /* ------------------------------------------------------------ */
    /* A handler that can be gracefully shutdown.
     * Called by doStop if a {@link #setGracefulShutdown} period is set.
     * TODO move this somewhere better
     */
    public interface Graceful extends Handler
    {
        public void setShutdown(boolean shutdown);
    }

    /* ------------------------------------------------------------ */
    public static void main(String[] args)
    {
        System.err.println(getVersion());
    }
}