View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.transport.impl.jetty;
17  
18  import org.eclipse.jetty.server.AbstractHttpConnection;
19  import org.eclipse.jetty.server.Connector;
20  import org.eclipse.jetty.server.Request;
21  import org.eclipse.jetty.server.Server;
22  import org.eclipse.jetty.server.bio.SocketConnector;
23  import org.eclipse.jetty.servlet.ServletContextHandler;
24  import org.eclipse.jetty.servlet.ServletHolder;
25  import org.eclipse.jetty.util.thread.ExecutorThreadPool;
26  import org.fourthline.cling.transport.spi.ServletContainerAdapter;
27  
28  import javax.servlet.Servlet;
29  import javax.servlet.http.HttpServletRequest;
30  import java.io.IOException;
31  import java.net.Socket;
32  import java.util.concurrent.ExecutorService;
33  import java.util.logging.Level;
34  import java.util.logging.Logger;
35  
36  /**
37   * A singleton wrapper of a <code>org.eclipse.jetty.server.Server</code>.
38   * <p>
39   * This {@link org.fourthline.cling.transport.spi.ServletContainerAdapter} starts
40   * a Jetty 8 instance on its own and stops it. Only one single context and servlet
41   * is registered, to handle UPnP requests.
42   * </p>
43   * <p>
44   * This implementation works on Android, dependencies are <code>jetty-server</code>
45   * and <code>jetty-servlet</code> Maven modules.
46   * </p>
47   *
48   * @author Christian Bauer
49   */
50  public class JettyServletContainer implements ServletContainerAdapter {
51  
52      final private static Logger log = Logger.getLogger(JettyServletContainer.class.getName());
53  
54      // Singleton
55      public static final JettyServletContainer INSTANCE = new JettyServletContainer();
56      private JettyServletContainer() {
57          resetServer();
58      }
59  
60      protected Server server;
61  
62      @Override
63      synchronized public void setExecutorService(ExecutorService executorService) {
64          if (INSTANCE.server.getThreadPool() == null) {
65              INSTANCE.server.setThreadPool(new ExecutorThreadPool(executorService) {
66                  @Override
67                  protected void doStop() throws Exception {
68                      // Do nothing, don't shut down the Cling ExecutorService when Jetty stops!
69                  }
70              });
71          }
72      }
73  
74      @Override
75      synchronized public int addConnector(String host, int port) throws IOException {
76          SocketConnector connector = new SocketConnector();
77          connector.setHost(host);
78          connector.setPort(port);
79  
80          // Open immediately so we can get the assigned local port
81          connector.open();
82  
83          // Only add if open() succeeded
84          server.addConnector(connector);
85  
86          // stats the connector if the server is started (server starts all connectors when started)
87          if (server.isStarted()) {
88              try {
89                  connector.start();
90              } catch (Exception ex) {
91                  log.severe("Couldn't start connector: " + connector + " " + ex);
92                  throw new RuntimeException(ex);
93              }
94          }
95          return connector.getLocalPort();
96      }
97  
98      @Override
99      synchronized public void removeConnector(String host, int port)  {
100         Connector[] connectors = server.getConnectors();
101         for (Connector connector : connectors) {
102             if (connector.getHost().equals(host) && connector.getLocalPort() == port) {
103                 if (connector.isStarted() || connector.isStarting()) {
104                     try {
105                         connector.stop();
106                     } catch (Exception ex) {
107                         log.severe("Couldn't stop connector: " + connector + " " + ex);
108                         throw new RuntimeException(ex);
109                     }
110                 }
111                 server.removeConnector(connector);
112                 if (connectors.length == 1) {
113                     log.info("No more connectors, stopping Jetty server");
114                     stopIfRunning();
115                 }
116                 break;
117             }
118         }
119     }
120 
121     @Override
122     synchronized public void registerServlet(String contextPath, Servlet servlet) {
123         if (server.getHandler() != null) {
124             return;
125         }
126         log.info("Registering UPnP servlet under context path: " + contextPath);
127         ServletContextHandler servletHandler =
128             new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
129         if (contextPath != null && contextPath.length() > 0)
130             servletHandler.setContextPath(contextPath);
131         ServletHolder s = new ServletHolder(servlet);
132         servletHandler.addServlet(s, "/*");
133         server.setHandler(servletHandler);
134     }
135 
136     @Override
137     synchronized public void startIfNotRunning() {
138         if (!server.isStarted() && !server.isStarting()) {
139             log.info("Starting Jetty server... ");
140             try {
141                 server.start();
142             } catch (Exception ex) {
143                 log.severe("Couldn't start Jetty server: " + ex);
144                 throw new RuntimeException(ex);
145             }
146         }
147     }
148 
149     @Override
150     synchronized public void stopIfRunning() {
151         if (!server.isStopped() && !server.isStopping()) {
152             log.info("Stopping Jetty server...");
153             try {
154                 server.stop();
155             } catch (Exception ex) {
156                 log.severe("Couldn't stop Jetty server: " + ex);
157                 throw new RuntimeException(ex);
158             } finally {
159                 resetServer();
160             }
161         }
162     }
163 
164     protected void resetServer() {
165         server = new Server(); // Has its own QueuedThreadPool
166         server.setGracefulShutdown(1000); // Let's wait a second for ongoing transfers to complete
167     }
168 
169     /**
170      * Casts the request to a Jetty API and tries to write a space character to the output stream of the socket.
171      * <p>
172      * This space character might confuse the HTTP client. The Cling transports for Jetty Client and
173      * Apache HttpClient have been tested to work with space characters. Unfortunately, Sun JDK's
174      * HttpURLConnection does not gracefully handle any garbage in the HTTP request!
175      * </p>
176      */
177     public static boolean isConnectionOpen(HttpServletRequest request) {
178         return isConnectionOpen(request, " ".getBytes());
179     }
180 
181     public static boolean isConnectionOpen(HttpServletRequest request, byte[] heartbeat) {
182         Request jettyRequest = (Request)request;
183         AbstractHttpConnection connection = jettyRequest.getConnection();
184         Socket socket = (Socket)connection.getEndPoint().getTransport();
185         if (log.isLoggable(Level.FINE))
186             log.fine("Checking if client connection is still open: " + socket.getRemoteSocketAddress());
187         try {
188             socket.getOutputStream().write(heartbeat);
189             socket.getOutputStream().flush();
190             return true;
191         } catch (IOException ex) {
192             if (log.isLoggable(Level.FINE))
193                 log.fine("Client connection has been closed: " + socket.getRemoteSocketAddress());
194             return false;
195         }
196     }
197 
198 }