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
.HttpsConfigurator
;
34 import com
.sun
.net
.httpserver
.HttpsServer
;
36 /** An {@link HttpServer} implementation based on Jetty. */
37 public class JettyHttpServer
extends HttpsServer
{
38 private final static CmsLog log
= CmsLog
.getLog(JettyHttpServer
.class);
40 private static final int DEFAULT_IDLE_TIMEOUT
= 30000;
42 private Server server
;
44 protected ServerConnector httpConnector
;
45 protected ServerConnector httpsConnector
;
47 private InetSocketAddress httpAddress
;
48 private InetSocketAddress httpsAddress
;
50 private ThreadPoolExecutor executor
;
52 private HttpsConfigurator httpsConfigurator
;
54 private final Map
<String
, JettyHttpContext
> contexts
= new TreeMap
<>();
56 private ServletContextHandler rootContextHandler
;
57 protected final ContextHandlerCollection contextHandlerCollection
= new ContextHandlerCollection();
59 private boolean started
;
61 private CmsState cmsState
;
64 public void bind(InetSocketAddress addr
, int backlog
) throws IOException
{
65 throw new UnsupportedOperationException();
72 ThreadPool threadPool
= null;
73 if (executor
!= null) {
74 threadPool
= new ExecutorThreadPool(executor
);
76 // TODO make it configurable
77 threadPool
= new QueuedThreadPool(10, 1);
80 server
= new Server(threadPool
);
82 configureConnectors();
84 if (httpConnector
!= null) {
86 server
.addConnector(httpConnector
);
89 if (httpsConnector
!= null) {
90 httpsConnector
.open();
91 server
.addConnector(httpsConnector
);
97 rootContextHandler
= createRootContextHandler();
98 // httpContext.addServlet(holder, "/*");
99 if (rootContextHandler
!= null)
100 configureRootContextHandler(rootContextHandler
);
102 if (rootContextHandler
!= null && !contexts
.containsKey("/"))
103 contextHandlerCollection
.addHandler(rootContextHandler
);
105 server
.setHandler(contextHandlerCollection
);
113 String httpHost
= getDeployProperty(CmsDeployProperty
.HOST
);
114 String fallBackHostname
= cmsState
!= null ? cmsState
.getHostname() : "::1";
115 if (httpConnector
!= null) {
116 httpAddress
= new InetSocketAddress(httpHost
!= null ? httpHost
: fallBackHostname
,
117 httpConnector
.getLocalPort());
118 } else if (httpsConnector
!= null) {
119 httpsAddress
= new InetSocketAddress(httpHost
!= null ? httpHost
: fallBackHostname
,
120 httpsConnector
.getLocalPort());
123 Runtime
.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
125 log
.info(httpPortsMsg());
127 } catch (Exception e
) {
129 throw new IllegalStateException("Cannot start Jetty HTTP server", e
);
133 protected void configureConnectors() {
134 String httpPortStr
= getDeployProperty(CmsDeployProperty
.HTTP_PORT
);
135 String httpsPortStr
= getDeployProperty(CmsDeployProperty
.HTTPS_PORT
);
136 if (httpPortStr
!= null && httpsPortStr
!= null)
137 throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
138 if (httpPortStr
== null && httpsPortStr
== null)
139 throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
141 /// TODO make it more generic
142 String httpHost
= getDeployProperty(CmsDeployProperty
.HOST
);
145 if (httpPortStr
!= null || httpsPortStr
!= null) {
146 // TODO deal with hostname resolving taking too much time
147 // String fallBackHostname = InetAddress.getLocalHost().getHostName();
149 boolean httpEnabled
= httpPortStr
!= null;
150 boolean httpsEnabled
= httpsPortStr
!= null;
153 HttpConfiguration httpConfiguration
= new HttpConfiguration();
155 if (httpsEnabled
) {// not supported anymore to have both http and https, but it may change again
156 int httpsPort
= Integer
.parseInt(httpsPortStr
);
157 httpConfiguration
.setSecureScheme("https");
158 httpConfiguration
.setSecurePort(httpsPort
);
161 int httpPort
= Integer
.parseInt(httpPortStr
);
162 httpConnector
= new ServerConnector(server
, new HttpConnectionFactory(httpConfiguration
));
163 httpConnector
.setPort(httpPort
);
164 httpConnector
.setHost(httpHost
);
165 httpConnector
.setIdleTimeout(DEFAULT_IDLE_TIMEOUT
);
170 SslContextFactory
.Server sslContextFactory
= new SslContextFactory
.Server();
171 // sslContextFactory.setKeyStore(KeyS)
173 sslContextFactory
.setKeyStoreType(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORETYPE
));
174 sslContextFactory
.setKeyStorePath(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORE
));
175 sslContextFactory
.setKeyStorePassword(getDeployProperty(CmsDeployProperty
.SSL_PASSWORD
));
176 // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
177 sslContextFactory
.setProtocol("TLS");
179 sslContextFactory
.setTrustStoreType(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORETYPE
));
180 sslContextFactory
.setTrustStorePath(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORE
));
181 sslContextFactory
.setTrustStorePassword(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTOREPASSWORD
));
183 String wantClientAuth
= getDeployProperty(CmsDeployProperty
.SSL_WANTCLIENTAUTH
);
184 if (wantClientAuth
!= null && wantClientAuth
.equals(Boolean
.toString(true)))
185 sslContextFactory
.setWantClientAuth(true);
186 String needClientAuth
= getDeployProperty(CmsDeployProperty
.SSL_NEEDCLIENTAUTH
);
187 if (needClientAuth
!= null && needClientAuth
.equals(Boolean
.toString(true)))
188 sslContextFactory
.setNeedClientAuth(true);
190 // HTTPS Configuration
191 HttpConfiguration httpsConfiguration
= new HttpConfiguration();
192 httpsConfiguration
.addCustomizer(new SecureRequestCustomizer());
193 httpsConfiguration
.setUriCompliance(UriCompliance
.LEGACY
);
196 httpsConnector
= new ServerConnector(server
, new SslConnectionFactory(sslContextFactory
, "http/1.1"),
197 new HttpConnectionFactory(httpsConfiguration
));
198 int httpsPort
= Integer
.parseInt(httpsPortStr
);
199 httpsConnector
.setPort(httpsPort
);
200 httpsConnector
.setHost(httpHost
);
206 public void stop(int delay
) {
207 // TODO wait for processing to complete
215 // TODO delete temp dir
217 } catch (Exception e
) {
218 log
.error("Cannot stop Jetty HTTP server", e
);
224 public void setExecutor(Executor executor
) {
225 if (!(executor
instanceof ThreadPoolExecutor
))
226 throw new IllegalArgumentException("Only " + ThreadPoolExecutor
.class.getName() + " are supported");
227 this.executor
= (ThreadPoolExecutor
) executor
;
231 public Executor
getExecutor() {
236 public synchronized HttpContext
createContext(String path
, HttpHandler handler
) {
237 HttpContext httpContext
= createContext(path
);
238 httpContext
.setHandler(handler
);
243 public synchronized HttpContext
createContext(String path
) {
244 if (!path
.endsWith("/"))
246 if (contexts
.containsKey(path
))
247 throw new IllegalArgumentException("Context " + path
+ " already exists");
249 JettyHttpContext httpContext
= new ServletHttpContext(this, path
);
250 contexts
.put(path
, httpContext
);
252 contextHandlerCollection
.addHandler(httpContext
.getServletContextHandler());
257 public synchronized void removeContext(String path
) throws IllegalArgumentException
{
258 if (!contexts
.containsKey(path
))
259 throw new IllegalArgumentException("Context " + path
+ " does not exist");
260 JettyHttpContext httpContext
= contexts
.remove(path
);
261 if (httpContext
instanceof ContextHandlerHttpContext contextHandlerHttpContext
) {
262 // TODO stop handler first?
263 contextHandlerCollection
.removeHandler(contextHandlerHttpContext
.getServletContextHandler());
268 public synchronized void removeContext(HttpContext context
) {
269 removeContext(context
.getPath());
273 public InetSocketAddress
getAddress() {
274 InetSocketAddress res
= httpAddress
!= null ? httpAddress
: httpsAddress
;
276 throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available");
281 public void setHttpsConfigurator(HttpsConfigurator config
) {
282 this.httpsConfigurator
= config
;
286 public HttpsConfigurator
getHttpsConfigurator() {
287 return httpsConfigurator
;
290 protected String
getDeployProperty(CmsDeployProperty deployProperty
) {
291 return cmsState
!= null ? cmsState
.getDeployProperty(deployProperty
.getProperty())
292 : System
.getProperty(deployProperty
.getProperty());
295 private String
httpPortsMsg() {
297 return (httpConnector
!= null ?
"HTTP " + getHttpPort() + " " : "")
298 + (httpsConnector
!= null ?
"HTTPS " + getHttpsPort() : "");
301 public Integer
getHttpPort() {
302 if (httpConnector
== null)
304 return httpConnector
.getLocalPort();
307 public Integer
getHttpsPort() {
308 if (httpsConnector
== null)
310 return httpsConnector
.getLocalPort();
313 protected ServletContextHandler
createRootContextHandler() {
317 protected void configureRootContextHandler(ServletContextHandler servletContextHandler
) throws ServletException
{
321 public void setCmsState(CmsState cmsState
) {
322 this.cmsState
= cmsState
;
325 boolean isStarted() {
329 ServletContextHandler
getRootContextHandler() {
330 return rootContextHandler
;
333 ServerContainer
getRootServerContainer() {
334 throw new UnsupportedOperationException();
337 public static void main(String
... args
) {
338 JettyHttpServer httpServer
= new JettyHttpServer();
339 System
.setProperty("argeo.http.port", "8080");
340 httpServer
.createContext("/", (exchange
) -> {
341 exchange
.getResponseBody().write("Hello World!".getBytes());
344 httpServer
.createContext("/sub/context", (exchange
) -> {
345 final String key
= "count";
346 Integer count
= (Integer
) exchange
.getHttpContext().getAttributes().get(key
);
348 exchange
.getHttpContext().getAttributes().put(key
, 0);
350 exchange
.getHttpContext().getAttributes().put(key
, count
+ 1);
351 StringBuilder sb
= new StringBuilder();
352 sb
.append("Subcontext:");
353 sb
.append(" " + key
+ "=" + exchange
.getHttpContext().getAttributes().get(key
));
354 sb
.append(" relativePath=" + HttpServerUtils
.relativize(exchange
));
355 exchange
.getResponseBody().write(sb
.toString().getBytes());