]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
Use latest build system
[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
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;
29
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;
34
35 public class JettyHttpServer extends HttpsServer {
36 private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class);
37
38 private static final int DEFAULT_IDLE_TIMEOUT = 30000;
39
40 private Server server;
41
42 protected ServerConnector httpConnector;
43 protected ServerConnector httpsConnector;
44
45 private InetSocketAddress httpAddress;
46 private InetSocketAddress httpsAddress;
47
48 private ThreadPoolExecutor executor;
49
50 private HttpsConfigurator httpsConfigurator;
51
52 private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
53
54 protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
55
56 private boolean started;
57
58 private CmsState cmsState;
59
60 @Override
61 public void bind(InetSocketAddress addr, int backlog) throws IOException {
62 throw new UnsupportedOperationException();
63 }
64
65 @Override
66 public void start() {
67 try {
68
69 ThreadPool threadPool = null;
70 if (executor != null) {
71 threadPool = new ExecutorThreadPool(executor);
72 } else {
73 // TODO make it configurable
74 threadPool = new QueuedThreadPool(10, 1);
75 }
76
77 server = new Server(threadPool);
78
79 configureConnectors();
80
81 if (httpConnector != null) {
82 httpConnector.open();
83 server.addConnector(httpConnector);
84 }
85
86 if (httpsConnector != null) {
87 httpsConnector.open();
88 server.addConnector(httpsConnector);
89 }
90
91 // holder
92
93 // context
94 ServletContextHandler rootContextHandler = createRootContextHandler();
95 // httpContext.addServlet(holder, "/*");
96 if (rootContextHandler != null)
97 configureRootContextHandler(rootContextHandler);
98 // server.setHandler(rootContextHandler);
99
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());
106 // }
107
108 server.setHandler(contextHandlerCollection);
109
110 //
111 // START
112 server.start();
113 //
114
115 // Addresses
116 String httpHost = getDeployProperty(CmsDeployProperty.HOST);
117 String fallBackHostname = cmsState != null ? cmsState.getHostname() : "::1";
118 if (httpConnector != null)
119 httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname,
120 httpConnector.getLocalPort());
121 if (httpsConnector != null)
122 httpsAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname,
123 httpsConnector.getLocalPort());
124
125 // Clean up
126 Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
127
128 log.info(httpPortsMsg());
129 started = true;
130 } catch (Exception e) {
131 throw new IllegalStateException("Cannot start Jetty HTTPS server", e);
132 }
133 }
134
135 @Override
136 public void stop(int delay) {
137 // TODO wait for processing to complete
138 stop();
139
140 }
141
142 public void stop() {
143 try {
144 // serverConnector.close();
145 server.stop();
146 // TODO delete temp dir
147 started = false;
148 } catch (Exception e) {
149 e.printStackTrace();
150 }
151
152 }
153
154 @Override
155 public void setExecutor(Executor executor) {
156 if (!(executor instanceof ThreadPoolExecutor))
157 throw new IllegalArgumentException("Only " + ThreadPoolExecutor.class.getName() + " are supported");
158 this.executor = (ThreadPoolExecutor) executor;
159 }
160
161 @Override
162 public Executor getExecutor() {
163 return executor;
164 }
165
166 @Override
167 public synchronized HttpContext createContext(String path, HttpHandler handler) {
168 HttpContext httpContext = createContext(path);
169 httpContext.setHandler(handler);
170 return httpContext;
171 }
172
173 @Override
174 public synchronized HttpContext createContext(String path) {
175 if (contexts.containsKey(path))
176 throw new IllegalArgumentException("Context " + path + " already exists");
177 JettyHttpContext httpContext = new JettyHttpContext(this, path);
178 contexts.put(path, httpContext);
179
180 contextHandlerCollection.addHandler(httpContext.getContextHandler());
181 return httpContext;
182 }
183
184 @Override
185 public synchronized void removeContext(String path) throws IllegalArgumentException {
186 if (!contexts.containsKey(path))
187 throw new IllegalArgumentException("Context " + path + " does not exist");
188 JettyHttpContext httpContext = contexts.remove(path);
189 // TODO stop handler first?
190 contextHandlerCollection.removeHandler(httpContext.getContextHandler());
191 }
192
193 @Override
194 public synchronized void removeContext(HttpContext context) {
195 removeContext(context.getPath());
196 }
197
198 @Override
199 public InetSocketAddress getAddress() {
200 return httpAddress;
201 }
202
203 @Override
204 public void setHttpsConfigurator(HttpsConfigurator config) {
205 this.httpsConfigurator = config;
206 }
207
208 @Override
209 public HttpsConfigurator getHttpsConfigurator() {
210 return httpsConfigurator;
211 }
212
213 protected void configureConnectors() {
214 HttpConfiguration httpConfiguration = new HttpConfiguration();
215
216 String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
217 String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
218
219 /// TODO make it more generic
220 String httpHost = getDeployProperty(CmsDeployProperty.HOST);
221 // String httpsHost = getFrameworkProp(
222 // JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
223
224 // try {
225 if (httpPortStr != null || httpsPortStr != null) {
226 // TODO deal with hostname resolving taking too much time
227 // String fallBackHostname = InetAddress.getLocalHost().getHostName();
228
229 boolean httpEnabled = httpPortStr != null;
230 // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
231 boolean httpsEnabled = httpsPortStr != null;
232 // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
233 if (httpsEnabled) {
234 int httpsPort = Integer.parseInt(httpsPortStr);
235 httpConfiguration.setSecureScheme("https");
236 httpConfiguration.setSecurePort(httpsPort);
237 }
238
239 if (httpEnabled) {
240 int httpPort = Integer.parseInt(httpPortStr);
241 httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
242 httpConnector.setPort(httpPort);
243 httpConnector.setHost(httpHost);
244 httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
245
246 }
247
248 if (httpsEnabled) {
249
250 SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
251 // sslContextFactory.setKeyStore(KeyS)
252
253 sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
254 sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
255 sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
256 // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
257 sslContextFactory.setProtocol("TLS");
258
259 sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
260 sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
261 sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
262
263 String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
264 if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
265 sslContextFactory.setWantClientAuth(true);
266 String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
267 if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
268 sslContextFactory.setNeedClientAuth(true);
269
270 // HTTPS Configuration
271 HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
272 https_config.addCustomizer(new SecureRequestCustomizer());
273 https_config.setUriCompliance(UriCompliance.LEGACY);
274
275 // HTTPS connector
276 httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
277 new HttpConnectionFactory(https_config));
278 int httpsPort = Integer.parseInt(httpsPortStr);
279 httpsConnector.setPort(httpsPort);
280 httpsConnector.setHost(httpHost);
281 }
282
283 }
284
285 }
286
287 protected String getDeployProperty(CmsDeployProperty deployProperty) {
288 return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
289 : System.getProperty(deployProperty.getProperty());
290 }
291
292 private String httpPortsMsg() {
293
294 return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
295 + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
296 }
297
298 public Integer getHttpPort() {
299 if (httpConnector == null)
300 return null;
301 return httpConnector.getLocalPort();
302 }
303
304 public Integer getHttpsPort() {
305 if (httpsConnector == null)
306 return null;
307 return httpsConnector.getLocalPort();
308 }
309
310 protected ServletContextHandler createRootContextHandler() {
311 return null;
312 }
313
314 protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
315
316 }
317
318 public void setCmsState(CmsState cmsState) {
319 this.cmsState = cmsState;
320 }
321
322 public boolean isStarted() {
323 return started;
324 }
325
326 public static void main(String... args) {
327 JettyHttpServer httpServer = new JettyHttpServer();
328 System.setProperty("argeo.http.port", "8080");
329 httpServer.createContext("/", (exchange) -> {
330 exchange.getResponseBody().write("Hello World!".getBytes());
331 });
332 httpServer.start();
333 httpServer.createContext("/sub/context", (exchange) -> {
334 final String key = "count";
335 Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
336 if (count == null)
337 exchange.getHttpContext().getAttributes().put(key, 0);
338 else
339 exchange.getHttpContext().getAttributes().put(key, count + 1);
340 StringBuilder sb = new StringBuilder();
341 sb.append("Subcontext:");
342 sb.append(" " + key + "=" + exchange.getHttpContext().getAttributes().get(key));
343 sb.append(" relativePath=" + HttpServerUtils.relativize(exchange));
344 exchange.getResponseBody().write(sb.toString().getBytes());
345 });
346 }
347 }