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
;
12 import org
.argeo
.api
.cms
.CmsLog
;
13 import org
.argeo
.api
.cms
.CmsState
;
14 import org
.argeo
.cms
.CmsDeployProperty
;
15 import org
.argeo
.util
.http
.HttpServerUtils
;
16 import org
.eclipse
.jetty
.http
.UriCompliance
;
17 import org
.eclipse
.jetty
.server
.HttpConfiguration
;
18 import org
.eclipse
.jetty
.server
.HttpConnectionFactory
;
19 import org
.eclipse
.jetty
.server
.SecureRequestCustomizer
;
20 import org
.eclipse
.jetty
.server
.Server
;
21 import org
.eclipse
.jetty
.server
.ServerConnector
;
22 import org
.eclipse
.jetty
.server
.SslConnectionFactory
;
23 import org
.eclipse
.jetty
.server
.handler
.ContextHandlerCollection
;
24 import org
.eclipse
.jetty
.servlet
.ServletContextHandler
;
25 import org
.eclipse
.jetty
.util
.ssl
.SslContextFactory
;
26 import org
.eclipse
.jetty
.util
.thread
.ExecutorThreadPool
;
27 import org
.eclipse
.jetty
.util
.thread
.QueuedThreadPool
;
28 import org
.eclipse
.jetty
.util
.thread
.ThreadPool
;
30 import com
.sun
.net
.httpserver
.HttpContext
;
31 import com
.sun
.net
.httpserver
.HttpHandler
;
32 import com
.sun
.net
.httpserver
.HttpsConfigurator
;
33 import com
.sun
.net
.httpserver
.HttpsServer
;
35 public class JettyHttpServer
extends HttpsServer
{
36 private final static CmsLog log
= CmsLog
.getLog(JettyHttpServer
.class);
38 private static final int DEFAULT_IDLE_TIMEOUT
= 30000;
40 private Server server
;
42 protected ServerConnector httpConnector
;
43 protected ServerConnector httpsConnector
;
45 private InetSocketAddress httpAddress
;
46 private InetSocketAddress httpsAddress
;
48 private ThreadPoolExecutor executor
;
50 private HttpsConfigurator httpsConfigurator
;
52 private final Map
<String
, JettyHttpContext
> contexts
= new TreeMap
<>();
54 protected final ContextHandlerCollection contextHandlerCollection
= new ContextHandlerCollection();
56 private boolean started
;
58 private CmsState cmsState
;
61 public void bind(InetSocketAddress addr
, int backlog
) throws IOException
{
62 throw new UnsupportedOperationException();
69 ThreadPool threadPool
= null;
70 if (executor
!= null) {
71 threadPool
= new ExecutorThreadPool(executor
);
73 // TODO make it configurable
74 threadPool
= new QueuedThreadPool(10, 1);
77 server
= new Server(threadPool
);
79 configureConnectors();
81 if (httpConnector
!= null) {
83 server
.addConnector(httpConnector
);
86 if (httpsConnector
!= null) {
87 httpsConnector
.open();
88 server
.addConnector(httpsConnector
);
94 ServletContextHandler rootContextHandler
= createRootContextHandler();
95 // httpContext.addServlet(holder, "/*");
96 if (rootContextHandler
!= null)
97 configureRootContextHandler(rootContextHandler
);
98 // server.setHandler(rootContextHandler);
100 // ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
101 if (rootContextHandler
!= null && !contexts
.containsKey("/"))
102 contextHandlerCollection
.addHandler(rootContextHandler
);
103 // for (String contextPath : contexts.keySet()) {
104 // JettyHttpContext ctx = contexts.get(contextPath);
105 // contextHandlers.addHandler(ctx.getContextHandler());
108 server
.setHandler(contextHandlerCollection
);
115 Runtime
.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
117 log
.info(httpPortsMsg());
119 } catch (Exception e
) {
120 throw new IllegalStateException("Cannot start Jetty HTTPS server", e
);
125 public void stop(int delay
) {
126 // TODO wait for processing to complete
133 // serverConnector.close();
135 // TODO delete temp dir
137 } catch (Exception e
) {
144 public void setExecutor(Executor executor
) {
145 if (!(executor
instanceof ThreadPoolExecutor
))
146 throw new IllegalArgumentException("Only " + ThreadPoolExecutor
.class.getName() + " are supported");
147 this.executor
= (ThreadPoolExecutor
) executor
;
151 public Executor
getExecutor() {
156 public synchronized HttpContext
createContext(String path
, HttpHandler handler
) {
157 HttpContext httpContext
= createContext(path
);
158 httpContext
.setHandler(handler
);
163 public synchronized HttpContext
createContext(String path
) {
164 if (contexts
.containsKey(path
))
165 throw new IllegalArgumentException("Context " + path
+ " already exists");
166 JettyHttpContext httpContext
= new JettyHttpContext(this, path
);
167 contexts
.put(path
, httpContext
);
169 contextHandlerCollection
.addHandler(httpContext
.getContextHandler());
174 public synchronized void removeContext(String path
) throws IllegalArgumentException
{
175 if (!contexts
.containsKey(path
))
176 throw new IllegalArgumentException("Context " + path
+ " does not exist");
177 JettyHttpContext httpContext
= contexts
.remove(path
);
178 // TODO stop handler first?
179 contextHandlerCollection
.removeHandler(httpContext
.getContextHandler());
183 public synchronized void removeContext(HttpContext context
) {
184 removeContext(context
.getPath());
188 public InetSocketAddress
getAddress() {
193 public void setHttpsConfigurator(HttpsConfigurator config
) {
194 this.httpsConfigurator
= config
;
198 public HttpsConfigurator
getHttpsConfigurator() {
199 return httpsConfigurator
;
202 protected void configureConnectors() {
203 HttpConfiguration httpConfiguration
= new HttpConfiguration();
205 String httpPortStr
= getDeployProperty(CmsDeployProperty
.HTTP_PORT
);
206 String httpsPortStr
= getDeployProperty(CmsDeployProperty
.HTTPS_PORT
);
208 /// TODO make it more generic
209 String httpHost
= getDeployProperty(CmsDeployProperty
.HOST
);
210 // String httpsHost = getFrameworkProp(
211 // JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
214 if (httpPortStr
!= null || httpsPortStr
!= null) {
215 // TODO deal with hostname resolving taking too much time
216 // String fallBackHostname = InetAddress.getLocalHost().getHostName();
217 String fallBackHostname
= cmsState
!= null ? cmsState
.getHostname() : "::1";
219 boolean httpEnabled
= httpPortStr
!= null;
220 // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
221 boolean httpsEnabled
= httpsPortStr
!= null;
222 // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
224 int httpsPort
= Integer
.parseInt(httpsPortStr
);
225 httpConfiguration
.setSecureScheme("https");
226 httpConfiguration
.setSecurePort(httpsPort
);
230 int httpPort
= Integer
.parseInt(httpPortStr
);
231 httpConnector
= new ServerConnector(server
, new HttpConnectionFactory(httpConfiguration
));
232 httpConnector
.setPort(httpPort
);
233 httpConnector
.setHost(httpHost
);
234 httpConnector
.setIdleTimeout(DEFAULT_IDLE_TIMEOUT
);
236 httpAddress
= new InetSocketAddress(httpHost
!= null ? httpHost
: fallBackHostname
, httpPort
);
241 SslContextFactory
.Server sslContextFactory
= new SslContextFactory
.Server();
242 // sslContextFactory.setKeyStore(KeyS)
244 sslContextFactory
.setKeyStoreType(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORETYPE
));
245 sslContextFactory
.setKeyStorePath(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORE
));
246 sslContextFactory
.setKeyStorePassword(getDeployProperty(CmsDeployProperty
.SSL_PASSWORD
));
247 // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
248 sslContextFactory
.setProtocol("TLS");
250 sslContextFactory
.setTrustStoreType(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORETYPE
));
251 sslContextFactory
.setTrustStorePath(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORE
));
252 sslContextFactory
.setTrustStorePassword(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTOREPASSWORD
));
254 String wantClientAuth
= getDeployProperty(CmsDeployProperty
.SSL_WANTCLIENTAUTH
);
255 if (wantClientAuth
!= null && wantClientAuth
.equals(Boolean
.toString(true)))
256 sslContextFactory
.setWantClientAuth(true);
257 String needClientAuth
= getDeployProperty(CmsDeployProperty
.SSL_NEEDCLIENTAUTH
);
258 if (needClientAuth
!= null && needClientAuth
.equals(Boolean
.toString(true)))
259 sslContextFactory
.setNeedClientAuth(true);
261 // HTTPS Configuration
262 HttpConfiguration https_config
= new HttpConfiguration(httpConfiguration
);
263 https_config
.addCustomizer(new SecureRequestCustomizer());
264 https_config
.setUriCompliance(UriCompliance
.LEGACY
);
267 httpsConnector
= new ServerConnector(server
, new SslConnectionFactory(sslContextFactory
, "http/1.1"),
268 new HttpConnectionFactory(https_config
));
269 int httpsPort
= Integer
.parseInt(httpsPortStr
);
270 httpsConnector
.setPort(httpsPort
);
271 httpsConnector
.setHost(httpHost
);
273 httpsAddress
= new InetSocketAddress(httpHost
!= null ? httpHost
: fallBackHostname
, httpsPort
);
280 protected String
getDeployProperty(CmsDeployProperty deployProperty
) {
281 return cmsState
!= null ? cmsState
.getDeployProperty(deployProperty
.getProperty())
282 : System
.getProperty(deployProperty
.getProperty());
285 private String
httpPortsMsg() {
287 return (httpConnector
!= null ?
"HTTP " + getHttpPort() + " " : " ")
288 + (httpsConnector
!= null ?
"HTTPS " + getHttpsPort() : "");
291 public Integer
getHttpPort() {
292 if (httpConnector
== null)
294 return httpConnector
.getLocalPort();
297 public Integer
getHttpsPort() {
298 if (httpsConnector
== null)
300 return httpsConnector
.getLocalPort();
303 protected ServletContextHandler
createRootContextHandler() {
307 protected void configureRootContextHandler(ServletContextHandler servletContextHandler
) throws ServletException
{
311 public void setCmsState(CmsState cmsState
) {
312 this.cmsState
= cmsState
;
315 public boolean isStarted() {
319 public static void main(String
... args
) {
320 JettyHttpServer httpServer
= new JettyHttpServer();
321 System
.setProperty("argeo.http.port", "8080");
322 httpServer
.createContext("/", (exchange
) -> {
323 exchange
.getResponseBody().write("Hello World!".getBytes());
326 httpServer
.createContext("/sub/context", (exchange
) -> {
327 final String key
= "count";
328 Integer count
= (Integer
) exchange
.getHttpContext().getAttributes().get(key
);
330 exchange
.getHttpContext().getAttributes().put(key
, 0);
332 exchange
.getHttpContext().getAttributes().put(key
, count
+ 1);
333 StringBuilder sb
= new StringBuilder();
334 sb
.append("Subcontext:");
335 sb
.append(" " + key
+ "=" + exchange
.getHttpContext().getAttributes().get(key
));
336 sb
.append(" relativePath=" + HttpServerUtils
.relativize(exchange
));
337 exchange
.getResponseBody().write(sb
.toString().getBytes());