1 package org
.argeo
.cms
.jetty
;
3 import java
.io
.IOException
;
4 import java
.net
.InetSocketAddress
;
6 import java
.util
.TreeMap
;
7 import java
.util
.concurrent
.Executor
;
8 import java
.util
.concurrent
.ThreadPoolExecutor
;
10 import javax
.servlet
.ServletException
;
11 import javax
.websocket
.server
.ServerContainer
;
13 import org
.argeo
.api
.cms
.CmsLog
;
14 import org
.argeo
.api
.cms
.CmsState
;
15 import org
.argeo
.cms
.CmsDeployProperty
;
16 import org
.argeo
.cms
.http
.server
.HttpServerUtils
;
17 import org
.eclipse
.jetty
.http
.UriCompliance
;
18 import org
.eclipse
.jetty
.server
.HttpConfiguration
;
19 import org
.eclipse
.jetty
.server
.HttpConnectionFactory
;
20 import org
.eclipse
.jetty
.server
.SecureRequestCustomizer
;
21 import org
.eclipse
.jetty
.server
.Server
;
22 import org
.eclipse
.jetty
.server
.ServerConnector
;
23 import org
.eclipse
.jetty
.server
.SslConnectionFactory
;
24 import org
.eclipse
.jetty
.server
.handler
.ContextHandlerCollection
;
25 import org
.eclipse
.jetty
.servlet
.ServletContextHandler
;
26 import org
.eclipse
.jetty
.util
.ssl
.SslContextFactory
;
27 import org
.eclipse
.jetty
.util
.thread
.ExecutorThreadPool
;
28 import org
.eclipse
.jetty
.util
.thread
.QueuedThreadPool
;
29 import org
.eclipse
.jetty
.util
.thread
.ThreadPool
;
31 import com
.sun
.net
.httpserver
.HttpContext
;
32 import com
.sun
.net
.httpserver
.HttpHandler
;
33 import com
.sun
.net
.httpserver
.HttpServer
;
34 import com
.sun
.net
.httpserver
.HttpsConfigurator
;
35 import com
.sun
.net
.httpserver
.HttpsServer
;
37 /** An {@link HttpServer} implementation based on Jetty. */
38 public class JettyHttpServer
extends HttpsServer
{
39 private final static CmsLog log
= CmsLog
.getLog(JettyHttpServer
.class);
41 /** Long timeout since our users may have poor connections. */
42 private static final int DEFAULT_IDLE_TIMEOUT
= 120 * 1000;
44 private Server server
;
46 protected ServerConnector httpConnector
;
47 protected ServerConnector httpsConnector
;
49 private InetSocketAddress httpAddress
;
50 private InetSocketAddress httpsAddress
;
52 private ThreadPoolExecutor executor
;
54 private HttpsConfigurator httpsConfigurator
;
56 private final Map
<String
, JettyHttpContext
> contexts
= new TreeMap
<>();
58 private ServletContextHandler rootContextHandler
;
59 protected final ContextHandlerCollection contextHandlerCollection
= new ContextHandlerCollection();
61 private boolean started
;
63 private CmsState cmsState
;
66 public void bind(InetSocketAddress addr
, int backlog
) throws IOException
{
67 throw new UnsupportedOperationException();
72 String httpPortStr
= getDeployProperty(CmsDeployProperty
.HTTP_PORT
);
73 String httpsPortStr
= getDeployProperty(CmsDeployProperty
.HTTPS_PORT
);
74 if (httpPortStr
!= null && httpsPortStr
!= null)
75 throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
76 if (httpPortStr
== null && httpsPortStr
== null) {
77 log
.warn("Neither an HTTP or an HTTPS port was configured, not starting Jetty");
80 /// TODO make it more generic
81 String httpHost
= getDeployProperty(CmsDeployProperty
.HOST
);
85 ThreadPool threadPool
= null;
86 if (executor
!= null) {
87 threadPool
= new ExecutorThreadPool(executor
);
89 // TODO make it configurable
90 threadPool
= new QueuedThreadPool(10, 1);
93 server
= new Server(threadPool
);
95 configureConnectors(httpPortStr
, httpsPortStr
, httpHost
);
97 if (httpConnector
!= null) {
99 server
.addConnector(httpConnector
);
102 if (httpsConnector
!= null) {
103 httpsConnector
.open();
104 server
.addConnector(httpsConnector
);
110 rootContextHandler
= createRootContextHandler();
111 // httpContext.addServlet(holder, "/*");
112 if (rootContextHandler
!= null)
113 configureRootContextHandler(rootContextHandler
);
115 if (rootContextHandler
!= null && !contexts
.containsKey("/"))
116 contextHandlerCollection
.addHandler(rootContextHandler
);
118 server
.setHandler(contextHandlerCollection
);
126 String fallBackHostname
= cmsState
!= null ? cmsState
.getHostname() : "::1";
127 if (httpConnector
!= null) {
128 httpAddress
= new InetSocketAddress(httpHost
!= null ? httpHost
: fallBackHostname
,
129 httpConnector
.getLocalPort());
130 } else if (httpsConnector
!= null) {
131 httpsAddress
= new InetSocketAddress(httpHost
!= null ? httpHost
: fallBackHostname
,
132 httpsConnector
.getLocalPort());
135 Runtime
.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
137 log
.info(httpPortsMsg());
139 } catch (Exception e
) {
141 throw new IllegalStateException("Cannot start Jetty HTTP server", e
);
145 protected void configureConnectors(String httpPortStr
, String httpsPortStr
, String httpHost
) {
148 if (httpPortStr
!= null || httpsPortStr
!= null) {
149 // TODO deal with hostname resolving taking too much time
150 // String fallBackHostname = InetAddress.getLocalHost().getHostName();
152 boolean httpEnabled
= httpPortStr
!= null;
153 boolean httpsEnabled
= httpsPortStr
!= null;
156 HttpConfiguration httpConfiguration
= new HttpConfiguration();
158 if (httpsEnabled
) {// not supported anymore to have both http and https, but it may change again
159 int httpsPort
= Integer
.parseInt(httpsPortStr
);
160 httpConfiguration
.setSecureScheme("https");
161 httpConfiguration
.setSecurePort(httpsPort
);
164 int httpPort
= Integer
.parseInt(httpPortStr
);
165 httpConnector
= new ServerConnector(server
, new HttpConnectionFactory(httpConfiguration
));
166 httpConnector
.setPort(httpPort
);
167 httpConnector
.setHost(httpHost
);
168 httpConnector
.setIdleTimeout(DEFAULT_IDLE_TIMEOUT
);
173 SslContextFactory
.Server sslContextFactory
= new SslContextFactory
.Server();
174 // sslContextFactory.setKeyStore(KeyS)
176 sslContextFactory
.setKeyStoreType(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORETYPE
));
177 sslContextFactory
.setKeyStorePath(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORE
));
178 sslContextFactory
.setKeyStorePassword(getDeployProperty(CmsDeployProperty
.SSL_PASSWORD
));
179 // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
180 sslContextFactory
.setProtocol("TLS");
182 sslContextFactory
.setTrustStoreType(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORETYPE
));
183 sslContextFactory
.setTrustStorePath(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORE
));
184 sslContextFactory
.setTrustStorePassword(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTOREPASSWORD
));
186 String wantClientAuth
= getDeployProperty(CmsDeployProperty
.SSL_WANTCLIENTAUTH
);
187 if (wantClientAuth
!= null && wantClientAuth
.equals(Boolean
.toString(true)))
188 sslContextFactory
.setWantClientAuth(true);
189 String needClientAuth
= getDeployProperty(CmsDeployProperty
.SSL_NEEDCLIENTAUTH
);
190 if (needClientAuth
!= null && needClientAuth
.equals(Boolean
.toString(true)))
191 sslContextFactory
.setNeedClientAuth(true);
193 // HTTPS Configuration
194 HttpConfiguration httpsConfiguration
= new HttpConfiguration();
195 httpsConfiguration
.addCustomizer(new SecureRequestCustomizer());
196 httpsConfiguration
.setUriCompliance(UriCompliance
.LEGACY
);
199 httpsConnector
= new ServerConnector(server
, new SslConnectionFactory(sslContextFactory
, "http/1.1"),
200 new HttpConnectionFactory(httpsConfiguration
));
201 int httpsPort
= Integer
.parseInt(httpsPortStr
);
202 httpsConnector
.setPort(httpsPort
);
203 httpsConnector
.setHost(httpHost
);
204 httpsConnector
.setIdleTimeout(DEFAULT_IDLE_TIMEOUT
);
210 public void stop(int delay
) {
211 // TODO wait for processing to complete
219 // TODO delete temp dir
221 } catch (Exception e
) {
222 log
.error("Cannot stop Jetty HTTP server", e
);
228 public void setExecutor(Executor executor
) {
229 if (!(executor
instanceof ThreadPoolExecutor
))
230 throw new IllegalArgumentException("Only " + ThreadPoolExecutor
.class.getName() + " are supported");
231 this.executor
= (ThreadPoolExecutor
) executor
;
235 public Executor
getExecutor() {
240 public synchronized HttpContext
createContext(String path
, HttpHandler handler
) {
241 HttpContext httpContext
= createContext(path
);
242 httpContext
.setHandler(handler
);
247 public synchronized HttpContext
createContext(String path
) {
248 if (!path
.endsWith("/"))
250 if (contexts
.containsKey(path
))
251 throw new IllegalArgumentException("Context " + path
+ " already exists");
253 JettyHttpContext httpContext
= new ServletHttpContext(this, path
);
254 contexts
.put(path
, httpContext
);
256 contextHandlerCollection
.addHandler(httpContext
.getServletContextHandler());
261 public synchronized void removeContext(String path
) throws IllegalArgumentException
{
262 if (!path
.endsWith("/"))
264 if (!contexts
.containsKey(path
))
265 throw new IllegalArgumentException("Context " + path
+ " does not exist");
266 JettyHttpContext httpContext
= contexts
.remove(path
);
267 if (httpContext
instanceof ContextHandlerHttpContext contextHandlerHttpContext
) {
268 // TODO stop handler first?
269 contextHandlerCollection
.removeHandler(contextHandlerHttpContext
.getServletContextHandler());
274 public synchronized void removeContext(HttpContext context
) {
275 removeContext(context
.getPath());
279 public InetSocketAddress
getAddress() {
280 InetSocketAddress res
= httpAddress
!= null ? httpAddress
: httpsAddress
;
282 throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available");
287 public void setHttpsConfigurator(HttpsConfigurator config
) {
288 this.httpsConfigurator
= config
;
292 public HttpsConfigurator
getHttpsConfigurator() {
293 return httpsConfigurator
;
296 protected String
getDeployProperty(CmsDeployProperty deployProperty
) {
297 return cmsState
!= null ? cmsState
.getDeployProperty(deployProperty
.getProperty())
298 : System
.getProperty(deployProperty
.getProperty());
301 private String
httpPortsMsg() {
303 return (httpConnector
!= null ?
"HTTP " + getHttpPort() + " " : "")
304 + (httpsConnector
!= null ?
"HTTPS " + getHttpsPort() : "");
307 public Integer
getHttpPort() {
308 if (httpConnector
== null)
310 return httpConnector
.getLocalPort();
313 public Integer
getHttpsPort() {
314 if (httpsConnector
== null)
316 return httpsConnector
.getLocalPort();
319 protected ServletContextHandler
createRootContextHandler() {
323 protected void configureRootContextHandler(ServletContextHandler servletContextHandler
) throws ServletException
{
327 public void setCmsState(CmsState cmsState
) {
328 this.cmsState
= cmsState
;
331 boolean isStarted() {
335 ServletContextHandler
getRootContextHandler() {
336 return rootContextHandler
;
339 ServerContainer
getRootServerContainer() {
340 throw new UnsupportedOperationException();
343 public static void main(String
... args
) {
344 JettyHttpServer httpServer
= new JettyHttpServer();
345 System
.setProperty("argeo.http.port", "8080");
346 httpServer
.createContext("/", (exchange
) -> {
347 exchange
.getResponseBody().write("Hello World!".getBytes());
350 httpServer
.createContext("/sub/context", (exchange
) -> {
351 final String key
= "count";
352 Integer count
= (Integer
) exchange
.getHttpContext().getAttributes().get(key
);
354 exchange
.getHttpContext().getAttributes().put(key
, 0);
356 exchange
.getHttpContext().getAttributes().put(key
, count
+ 1);
357 StringBuilder sb
= new StringBuilder();
358 sb
.append("Subcontext:");
359 sb
.append(" " + key
+ "=" + exchange
.getHttpContext().getAttributes().get(key
));
360 sb
.append(" relativePath=" + HttpServerUtils
.relativize(exchange
));
361 exchange
.getResponseBody().write(sb
.toString().getBytes());