]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
Move Equinox specific code to the appropriate variant
[lgpl/argeo-commons.git] / org.argeo.cms.lib.jetty / src / org / argeo / cms / jetty / JettyHttpServer.java
1 package org.argeo.cms.jetty;
2
3 import java.io.IOException;
4 import java.net.InetSocketAddress;
5 import java.util.Map;
6 import java.util.TreeMap;
7 import java.util.concurrent.Executor;
8 import java.util.concurrent.ThreadPoolExecutor;
9
10 import javax.servlet.ServletException;
11 import javax.websocket.server.ServerContainer;
12
13 import org.argeo.api.cms.CmsLog;
14 import org.argeo.api.cms.CmsState;
15 import org.argeo.cms.CmsDeployProperty;
16 import org.argeo.util.http.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;
30
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;
35
36 /** An {@link HttpServer} implementation based on Jetty. */
37 public class JettyHttpServer extends HttpsServer {
38 private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class);
39
40 private static final int DEFAULT_IDLE_TIMEOUT = 30000;
41
42 private Server server;
43
44 protected ServerConnector httpConnector;
45 protected ServerConnector httpsConnector;
46
47 private InetSocketAddress httpAddress;
48 private InetSocketAddress httpsAddress;
49
50 private ThreadPoolExecutor executor;
51
52 private HttpsConfigurator httpsConfigurator;
53
54 private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
55
56 private ServletContextHandler rootContextHandler;
57 protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
58
59 private boolean started;
60
61 private CmsState cmsState;
62
63 @Override
64 public void bind(InetSocketAddress addr, int backlog) throws IOException {
65 throw new UnsupportedOperationException();
66 }
67
68 @Override
69 public void start() {
70 try {
71
72 ThreadPool threadPool = null;
73 if (executor != null) {
74 threadPool = new ExecutorThreadPool(executor);
75 } else {
76 // TODO make it configurable
77 threadPool = new QueuedThreadPool(10, 1);
78 }
79
80 server = new Server(threadPool);
81
82 configureConnectors();
83
84 if (httpConnector != null) {
85 httpConnector.open();
86 server.addConnector(httpConnector);
87 }
88
89 if (httpsConnector != null) {
90 httpsConnector.open();
91 server.addConnector(httpsConnector);
92 }
93
94 // holder
95
96 // context
97 rootContextHandler = createRootContextHandler();
98 // httpContext.addServlet(holder, "/*");
99 if (rootContextHandler != null)
100 configureRootContextHandler(rootContextHandler);
101
102 if (rootContextHandler != null && !contexts.containsKey("/"))
103 contextHandlerCollection.addHandler(rootContextHandler);
104
105 server.setHandler(contextHandlerCollection);
106
107 //
108 // START
109 server.start();
110 //
111
112 // Addresses
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());
121 }
122 // Clean up
123 Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
124
125 log.info(httpPortsMsg());
126 started = true;
127 } catch (Exception e) {
128 stop();
129 throw new IllegalStateException("Cannot start Jetty HTTP server", e);
130 }
131 }
132
133 protected void configureConnectors() {
134 HttpConfiguration httpConfiguration = new HttpConfiguration();
135
136 String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
137 String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
138 if (httpPortStr != null && httpsPortStr != null)
139 throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
140 if (httpPortStr == null && httpsPortStr == null)
141 throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
142
143 /// TODO make it more generic
144 String httpHost = getDeployProperty(CmsDeployProperty.HOST);
145
146 // try {
147 if (httpPortStr != null || httpsPortStr != null) {
148 // TODO deal with hostname resolving taking too much time
149 // String fallBackHostname = InetAddress.getLocalHost().getHostName();
150
151 boolean httpEnabled = httpPortStr != null;
152 boolean httpsEnabled = httpsPortStr != null;
153
154 if (httpsEnabled) {
155 int httpsPort = Integer.parseInt(httpsPortStr);
156 httpConfiguration.setSecureScheme("https");
157 httpConfiguration.setSecurePort(httpsPort);
158 }
159
160 if (httpEnabled) {
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);
166
167 }
168
169 if (httpsEnabled) {
170 SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
171 // sslContextFactory.setKeyStore(KeyS)
172
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");
178
179 sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
180 sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
181 sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
182
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);
189
190 // HTTPS Configuration
191 HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
192 https_config.addCustomizer(new SecureRequestCustomizer());
193 https_config.setUriCompliance(UriCompliance.LEGACY);
194
195 // HTTPS connector
196 httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
197 new HttpConnectionFactory(https_config));
198 int httpsPort = Integer.parseInt(httpsPortStr);
199 httpsConnector.setPort(httpsPort);
200 httpsConnector.setHost(httpHost);
201 }
202 }
203 }
204
205 @Override
206 public void stop(int delay) {
207 // TODO wait for processing to complete
208 stop();
209
210 }
211
212 public void stop() {
213 try {
214 server.stop();
215 // TODO delete temp dir
216 started = false;
217 } catch (Exception e) {
218 log.error("Cannot stop Jetty HTTP server", e);
219 }
220
221 }
222
223 @Override
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;
228 }
229
230 @Override
231 public Executor getExecutor() {
232 return executor;
233 }
234
235 @Override
236 public synchronized HttpContext createContext(String path, HttpHandler handler) {
237 HttpContext httpContext = createContext(path);
238 httpContext.setHandler(handler);
239 return httpContext;
240 }
241
242 @Override
243 public synchronized HttpContext createContext(String path) {
244 if (contexts.containsKey(path))
245 throw new IllegalArgumentException("Context " + path + " already exists");
246 if (!path.endsWith("/"))
247 throw new IllegalArgumentException("Path " + path + " should end with a /");
248
249 JettyHttpContext httpContext = new ServletHttpContext(this, path);
250 contexts.put(path, httpContext);
251
252 contextHandlerCollection.addHandler(httpContext.getServletContextHandler());
253 return httpContext;
254 }
255
256 @Override
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());
264 }
265 }
266
267 @Override
268 public synchronized void removeContext(HttpContext context) {
269 removeContext(context.getPath());
270 }
271
272 @Override
273 public InetSocketAddress getAddress() {
274 InetSocketAddress res = httpAddress != null ? httpAddress : httpsAddress;
275 if (res == null)
276 throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available");
277 return res;
278 }
279
280 @Override
281 public void setHttpsConfigurator(HttpsConfigurator config) {
282 this.httpsConfigurator = config;
283 }
284
285 @Override
286 public HttpsConfigurator getHttpsConfigurator() {
287 return httpsConfigurator;
288 }
289
290 protected String getDeployProperty(CmsDeployProperty deployProperty) {
291 return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
292 : System.getProperty(deployProperty.getProperty());
293 }
294
295 private String httpPortsMsg() {
296
297 return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "")
298 + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
299 }
300
301 public Integer getHttpPort() {
302 if (httpConnector == null)
303 return null;
304 return httpConnector.getLocalPort();
305 }
306
307 public Integer getHttpsPort() {
308 if (httpsConnector == null)
309 return null;
310 return httpsConnector.getLocalPort();
311 }
312
313 protected ServletContextHandler createRootContextHandler() {
314 return null;
315 }
316
317 protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
318
319 }
320
321 public void setCmsState(CmsState cmsState) {
322 this.cmsState = cmsState;
323 }
324
325 boolean isStarted() {
326 return started;
327 }
328
329 ServletContextHandler getRootContextHandler() {
330 return rootContextHandler;
331 }
332
333 ServerContainer getRootServerContainer() {
334 throw new UnsupportedOperationException();
335 }
336
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());
342 });
343 httpServer.start();
344 httpServer.createContext("/sub/context", (exchange) -> {
345 final String key = "count";
346 Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
347 if (count == null)
348 exchange.getHttpContext().getAttributes().put(key, 0);
349 else
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());
356 });
357 }
358 }