]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
Do not offer locale choice if there is only one.
[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 Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
116
117 log.info(httpPortsMsg());
118 started = true;
119 } catch (Exception e) {
120 throw new IllegalStateException("Cannot start Jetty HTTPS server", e);
121 }
122 }
123
124 @Override
125 public void stop(int delay) {
126 // TODO wait for processing to complete
127 stop();
128
129 }
130
131 public void stop() {
132 try {
133 // serverConnector.close();
134 server.stop();
135 // TODO delete temp dir
136 started = false;
137 } catch (Exception e) {
138 e.printStackTrace();
139 }
140
141 }
142
143 @Override
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;
148 }
149
150 @Override
151 public Executor getExecutor() {
152 return executor;
153 }
154
155 @Override
156 public synchronized HttpContext createContext(String path, HttpHandler handler) {
157 HttpContext httpContext = createContext(path);
158 httpContext.setHandler(handler);
159 return httpContext;
160 }
161
162 @Override
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);
168
169 contextHandlerCollection.addHandler(httpContext.getContextHandler());
170 return httpContext;
171 }
172
173 @Override
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());
180 }
181
182 @Override
183 public synchronized void removeContext(HttpContext context) {
184 removeContext(context.getPath());
185 }
186
187 @Override
188 public InetSocketAddress getAddress() {
189 return httpAddress;
190 }
191
192 @Override
193 public void setHttpsConfigurator(HttpsConfigurator config) {
194 this.httpsConfigurator = config;
195 }
196
197 @Override
198 public HttpsConfigurator getHttpsConfigurator() {
199 return httpsConfigurator;
200 }
201
202 protected void configureConnectors() {
203 HttpConfiguration httpConfiguration = new HttpConfiguration();
204
205 String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
206 String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
207
208 /// TODO make it more generic
209 String httpHost = getDeployProperty(CmsDeployProperty.HOST);
210 // String httpsHost = getFrameworkProp(
211 // JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
212
213 // try {
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";
218
219 boolean httpEnabled = httpPortStr != null;
220 // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
221 boolean httpsEnabled = httpsPortStr != null;
222 // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
223 if (httpsEnabled) {
224 int httpsPort = Integer.parseInt(httpsPortStr);
225 httpConfiguration.setSecureScheme("https");
226 httpConfiguration.setSecurePort(httpsPort);
227 }
228
229 if (httpEnabled) {
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);
235
236 httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, httpPort);
237 }
238
239 if (httpsEnabled) {
240
241 SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
242 // sslContextFactory.setKeyStore(KeyS)
243
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");
249
250 sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
251 sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
252 sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
253
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);
260
261 // HTTPS Configuration
262 HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
263 https_config.addCustomizer(new SecureRequestCustomizer());
264 https_config.setUriCompliance(UriCompliance.LEGACY);
265
266 // HTTPS connector
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);
272
273 httpsAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, httpsPort);
274 }
275
276 }
277
278 }
279
280 protected String getDeployProperty(CmsDeployProperty deployProperty) {
281 return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
282 : System.getProperty(deployProperty.getProperty());
283 }
284
285 private String httpPortsMsg() {
286
287 return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
288 + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
289 }
290
291 public Integer getHttpPort() {
292 if (httpConnector == null)
293 return null;
294 return httpConnector.getLocalPort();
295 }
296
297 public Integer getHttpsPort() {
298 if (httpsConnector == null)
299 return null;
300 return httpsConnector.getLocalPort();
301 }
302
303 protected ServletContextHandler createRootContextHandler() {
304 return null;
305 }
306
307 protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
308
309 }
310
311 public void setCmsState(CmsState cmsState) {
312 this.cmsState = cmsState;
313 }
314
315 public boolean isStarted() {
316 return started;
317 }
318
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());
324 });
325 httpServer.start();
326 httpServer.createContext("/sub/context", (exchange) -> {
327 final String key = "count";
328 Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
329 if (count == null)
330 exchange.getHttpContext().getAttributes().put(key, 0);
331 else
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());
338 });
339 }
340 }