]> git.argeo.org Git - lgpl/argeo-commons.git/blob - eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java
Improve initialisation.
[lgpl/argeo-commons.git] / eclipse / org.argeo.cms.servlet / src / org / argeo / cms / servlet / internal / jetty / JettyConfig.java
1 package org.argeo.cms.servlet.internal.jetty;
2
3 import java.io.IOException;
4 import java.io.Reader;
5 import java.nio.charset.StandardCharsets;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.security.KeyStore;
9 import java.util.Dictionary;
10 import java.util.Hashtable;
11 import java.util.Map;
12 import java.util.concurrent.ForkJoinPool;
13
14 import javax.websocket.DeploymentException;
15 import javax.websocket.server.ServerContainer;
16 import javax.websocket.server.ServerEndpointConfig;
17
18 import org.argeo.api.cms.CmsConstants;
19 import org.argeo.api.cms.CmsLog;
20 import org.argeo.api.cms.CmsState;
21 import org.argeo.cms.security.PkiUtils;
22 import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator;
23 import org.argeo.cms.websocket.javax.server.TestEndpoint;
24 import org.argeo.util.LangUtils;
25 import org.eclipse.equinox.http.jetty.JettyConfigurator;
26 import org.osgi.framework.BundleContext;
27 import org.osgi.framework.FrameworkUtil;
28 import org.osgi.framework.ServiceReference;
29 import org.osgi.util.tracker.ServiceTracker;
30
31 public class JettyConfig {
32 private final static CmsLog log = CmsLog.getLog(JettyConfig.class);
33
34 final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
35 // Argeo specific
36 final static String WEBSOCKET_ENABLED = "websocket.enabled";
37
38 private CmsState cmsState;
39
40 private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext();
41
42 public void start() {
43 // We need to start asynchronously so that Jetty bundle get started by lazy init
44 // due to the non-configurable behaviour of its activator
45 ForkJoinPool.commonPool().execute(() -> {
46 Dictionary<String, ?> properties = getHttpServerConfig();
47 startServer(properties);
48 });
49
50 ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
51 bc, ServerContainer.class, null) {
52
53 @Override
54 public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
55 ServerContainer serverContainer = super.addingService(reference);
56
57 BundleContext bc = reference.getBundle().getBundleContext();
58 ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
59 .getServiceReference(ServerEndpointConfig.Configurator.class);
60 ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
61 ServerEndpointConfig config = ServerEndpointConfig.Builder
62 .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
63 try {
64 serverContainer.addEndpoint(config);
65 } catch (DeploymentException e) {
66 throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
67 }
68 return serverContainer;
69 }
70
71 };
72 serverSt.open();
73
74 // check initialisation
75 // ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
76 //
77 // @Override
78 // public HttpService addingService(ServiceReference<HttpService> sr) {
79 // Object httpPort = sr.getProperty("http.port");
80 // Object httpsPort = sr.getProperty("https.port");
81 // log.info(httpPortsMsg(httpPort, httpsPort));
82 // close();
83 // return super.addingService(sr);
84 // }
85 // };
86 // httpSt.open();
87 }
88
89 public void stop() {
90 try {
91 JettyConfigurator.stopServer(CmsConstants.DEFAULT);
92 } catch (Exception e) {
93 log.error("Cannot stop default Jetty server.", e);
94 }
95
96 }
97
98 public void startServer(Dictionary<String, ?> properties) {
99 // Explicitly configures Jetty so that the default server is not started by the
100 // activator of the Equinox Jetty bundle.
101 Map<String, String> config = LangUtils.dictToStringMap(properties);
102 if (!config.isEmpty()) {
103 config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
104
105 // TODO centralise with Jetty extender
106 Object webSocketEnabled = config.get(WEBSOCKET_ENABLED);
107 if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
108 bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
109 config.put(WEBSOCKET_ENABLED, "true");
110 }
111 }
112
113 int tryCount = 30;
114 try {
115 tryGettyJetty: while (tryCount > 0) {
116 try {
117 // FIXME deal with multiple ids
118 JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
119
120 Object httpPort = config.get(InternalHttpConstants.HTTP_PORT);
121 Object httpsPort = config.get(InternalHttpConstants.HTTPS_PORT);
122 log.info(httpPortsMsg(httpPort, httpsPort));
123
124 // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
125 // configuration is not cleaned
126 FrameworkUtil.getBundle(JettyConfigurator.class).start();
127 break tryGettyJetty;
128 } catch (IllegalStateException e) {
129 // e.printStackTrace();
130 // Jetty may not be ready
131 try {
132 Thread.sleep(1000);
133 } catch (Exception e1) {
134 // silent
135 }
136 tryCount--;
137 }
138 }
139 } catch (Exception e) {
140 log.error("Cannot start default Jetty server with config " + properties, e);
141 }
142
143 }
144
145 private String httpPortsMsg(Object httpPort, Object httpsPort) {
146 return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
147 }
148
149 /** Override the provided config with the framework properties */
150 public Dictionary<String, Object> getHttpServerConfig() {
151 String httpPort = getFrameworkProp("org.osgi.service.http.port");
152 String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
153 /// TODO make it more generic
154 String httpHost = getFrameworkProp(
155 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
156 String httpsHost = getFrameworkProp(
157 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
158 String webSocketEnabled = getFrameworkProp(
159 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
160
161 final Hashtable<String, Object> props = new Hashtable<String, Object>();
162 // try {
163 if (httpPort != null || httpsPort != null) {
164 boolean httpEnabled = httpPort != null;
165 props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
166 boolean httpsEnabled = httpsPort != null;
167 props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
168
169 if (httpEnabled) {
170 props.put(InternalHttpConstants.HTTP_PORT, httpPort);
171 if (httpHost != null)
172 props.put(InternalHttpConstants.HTTP_HOST, httpHost);
173 }
174
175 if (httpsEnabled) {
176 props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
177 if (httpsHost != null)
178 props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
179
180 // server certificate
181 Path keyStorePath = cmsState.getDataPath(PkiUtils.DEFAULT_KEYSTORE_PATH);
182 Path pemKeyPath = cmsState.getDataPath(PkiUtils.DEFAULT_PEM_KEY_PATH);
183 Path pemCertPath = cmsState.getDataPath(PkiUtils.DEFAULT_PEM_CERT_PATH);
184 String keyStorePasswordStr = getFrameworkProp(
185 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
186 char[] keyStorePassword;
187 if (keyStorePasswordStr == null)
188 keyStorePassword = "changeit".toCharArray();
189 else
190 keyStorePassword = keyStorePasswordStr.toCharArray();
191
192 // if PEM files both exists, update the PKCS12 file
193 if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
194 // TODO check certificate update time? monitor changes?
195 KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
196 try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
197 Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
198 PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
199 PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
200 if (log.isDebugEnabled())
201 log.debug("PEM certificate stored in " + keyStorePath);
202 } catch (IOException e) {
203 log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
204 }
205 }
206
207 if (!Files.exists(keyStorePath))
208 PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
209 props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
210 props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
211 props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
212
213 // props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
214 // props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
215 // props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
216
217 // client certificate authentication
218 String wantClientAuth = getFrameworkProp(
219 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
220 if (wantClientAuth != null)
221 props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
222 String needClientAuth = getFrameworkProp(
223 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
224 if (needClientAuth != null)
225 props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
226 }
227
228 // web socket
229 if (webSocketEnabled != null && webSocketEnabled.equals("true"))
230 props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
231
232 props.put(CmsConstants.CN, CmsConstants.DEFAULT);
233 }
234 return props;
235 }
236
237 private String getFrameworkProp(String key) {
238 return cmsState.getDeployProperty(key);
239 }
240
241 public void setCmsState(CmsState cmsState) {
242 this.cmsState = cmsState;
243 }
244
245 }