1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static java.nio.charset.StandardCharsets.ISO_8859_1;
18
19 import java.io.IOException;
20 import java.net.BindException;
21 import java.net.URL;
22 import java.nio.charset.Charset;
23 import java.time.Duration;
24 import java.util.List;
25 import java.util.Map;
26
27 import javax.servlet.Servlet;
28
29 import org.eclipse.jetty.http.MimeTypes;
30 import org.eclipse.jetty.security.ConstraintMapping;
31 import org.eclipse.jetty.security.ConstraintSecurityHandler;
32 import org.eclipse.jetty.security.HashLoginService;
33 import org.eclipse.jetty.server.Connector;
34 import org.eclipse.jetty.server.Handler;
35 import org.eclipse.jetty.server.HttpConfiguration;
36 import org.eclipse.jetty.server.HttpConnectionFactory;
37 import org.eclipse.jetty.server.SecureRequestCustomizer;
38 import org.eclipse.jetty.server.Server;
39 import org.eclipse.jetty.server.ServerConnector;
40 import org.eclipse.jetty.server.SslConnectionFactory;
41 import org.eclipse.jetty.server.handler.HandlerList;
42 import org.eclipse.jetty.server.handler.HandlerWrapper;
43 import org.eclipse.jetty.server.handler.ResourceHandler;
44 import org.eclipse.jetty.servlet.DefaultServlet;
45 import org.eclipse.jetty.util.security.Constraint;
46 import org.eclipse.jetty.util.thread.QueuedThreadPool;
47 import org.eclipse.jetty.webapp.WebAppClassLoader;
48 import org.eclipse.jetty.webapp.WebAppContext;
49 import org.htmlunit.WebDriverTestCase.MockWebConnectionServlet;
50 import org.htmlunit.html.HtmlPage;
51 import org.htmlunit.util.MimeType;
52 import org.junit.After;
53
54
55
56
57
58
59
60
61
62
63
64 public abstract class WebServerTestCase extends WebTestCase {
65
66
67 public static final int BIND_TIMEOUT = 1000;
68
69 private Server server_;
70 private static Server STATIC_SERVER_;
71 private WebClient webClient_;
72 private CollectingAlertHandler alertHandler_ = new CollectingAlertHandler();
73
74
75
76
77
78
79
80
81
82 protected void startWebServer(final String resourceBase) throws Exception {
83 if (server_ != null) {
84 throw new IllegalStateException("startWebServer() can not be called twice");
85 }
86 final Server server = buildServer(PORT);
87
88 final WebAppContext context = new WebAppContext();
89 context.setContextPath("/");
90 context.setResourceBase(resourceBase);
91
92 final ResourceHandler resourceHandler = new ResourceHandler();
93 resourceHandler.setResourceBase(resourceBase);
94 final MimeTypes mimeTypes = new MimeTypes();
95 mimeTypes.addMimeMapping("js", MimeType.TEXT_JAVASCRIPT);
96 resourceHandler.setMimeTypes(mimeTypes);
97
98 final HandlerList handlers = new HandlerList();
99 handlers.setHandlers(new Handler[]{resourceHandler, context});
100 server.setHandler(handlers);
101 server.setHandler(resourceHandler);
102
103 tryStart(PORT, server);
104 server_ = server;
105 }
106
107
108
109
110
111
112
113
114
115
116 protected void startWebServer(final String resourceBase, final String[] classpath) throws Exception {
117 if (server_ != null) {
118 throw new IllegalStateException("startWebServer() can not be called twice");
119 }
120 server_ = createWebServer(resourceBase, classpath);
121 }
122
123
124
125
126
127
128
129
130
131
132
133
134
135 public static Server createWebServer(final String resourceBase, final String[] classpath) throws Exception {
136 return createWebServer(PORT, resourceBase, classpath, null, null);
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 public static Server createWebServer(final int port, final String resourceBase, final String[] classpath,
155 final Map<String, Class<? extends Servlet>> servlets, final HandlerWrapper handler) throws Exception {
156
157 final Server server = buildServer(port);
158
159 final WebAppContext context = new WebAppContext();
160 context.setContextPath("/");
161 context.setResourceBase(resourceBase);
162
163 if (servlets != null) {
164 for (final Map.Entry<String, Class<? extends Servlet>> entry : servlets.entrySet()) {
165 final String pathSpec = entry.getKey();
166 final Class<? extends Servlet> servlet = entry.getValue();
167 context.addServlet(servlet, pathSpec);
168
169
170 if ("/".equals(pathSpec)) {
171 context.setDefaultsDescriptor(null);
172 context.addServlet(DefaultServlet.class, "/favicon.ico");
173 }
174 }
175 }
176
177 final WebAppClassLoader loader = new WebAppClassLoader(context);
178 if (classpath != null) {
179 for (final String path : classpath) {
180 loader.addClassPath(path);
181 }
182 }
183 context.setClassLoader(loader);
184 if (handler != null) {
185 handler.setHandler(context);
186 server.setHandler(handler);
187 }
188 else {
189 server.setHandler(context);
190 }
191
192 tryStart(port, server);
193 return server;
194 }
195
196
197
198
199
200
201
202
203
204
205
206 protected void startWebServer(final String resourceBase, final String[] classpath,
207 final Map<String, Class<? extends Servlet>> servlets) throws Exception {
208 if (server_ != null) {
209 throw new IllegalStateException("startWebServer() can not be called twice");
210 }
211 final Server server = buildServer(PORT);
212
213 final WebAppContext context = new WebAppContext();
214 context.setContextPath("/");
215 context.setResourceBase(resourceBase);
216
217 for (final Map.Entry<String, Class<? extends Servlet>> entry : servlets.entrySet()) {
218 final String pathSpec = entry.getKey();
219 final Class<? extends Servlet> servlet = entry.getValue();
220 context.addServlet(servlet, pathSpec);
221 }
222 final WebAppClassLoader loader = new WebAppClassLoader(context);
223 if (classpath != null) {
224 for (final String path : classpath) {
225 loader.addClassPath(path);
226 }
227 }
228 context.setClassLoader(loader);
229 server.setHandler(context);
230
231 tryStart(PORT, server);
232 server_ = server;
233 }
234
235
236
237
238
239 @After
240 public void tearDown() throws Exception {
241 if (server_ != null) {
242 server_.stop();
243 server_.destroy();
244 server_ = null;
245 }
246
247 stopWebServer();
248 }
249
250
251
252
253
254
255
256
257
258 protected final HtmlPage loadPageWithAlerts(final String html, final URL url)
259 throws Exception {
260 return loadPageWithAlerts(html, url, Duration.ofSeconds(0));
261 }
262
263
264
265
266
267
268
269
270
271 protected final HtmlPage loadPageWithAlerts(final String html, final URL url, final Duration maxWaitTime)
272 throws Exception {
273 alertHandler_.clear();
274 expandExpectedAlertsVariables(URL_FIRST);
275
276 final String[] expectedAlerts = getExpectedAlerts();
277 final HtmlPage page = loadPage(html, url);
278
279 List<String> actualAlerts = getCollectedAlerts(page);
280 final long maxWait = System.currentTimeMillis() + maxWaitTime.toMillis();
281 while (actualAlerts.size() < expectedAlerts.length && System.currentTimeMillis() < maxWait) {
282 Thread.sleep(30);
283 actualAlerts = getCollectedAlerts(page);
284 }
285
286 assertEquals(expectedAlerts, getCollectedAlerts(page));
287 return page;
288 }
289
290
291
292
293
294
295
296
297 protected final HtmlPage loadPage(final String html) throws Exception {
298 return loadPage(html, URL_FIRST);
299 }
300
301
302
303
304
305
306
307
308 protected final HtmlPage loadPage(final String html, final URL url) throws Exception {
309 return loadPage(html, url, MimeType.TEXT_HTML, ISO_8859_1);
310 }
311
312
313
314
315
316
317
318
319
320
321 private HtmlPage loadPage(final String html, final URL url,
322 final String contentType, final Charset charset) throws Exception {
323 final MockWebConnection mockWebConnection = getMockWebConnection();
324 mockWebConnection.setResponse(url, html, contentType, charset);
325 startWebServer(mockWebConnection);
326
327 return getWebClient().getPage(url);
328 }
329
330
331
332
333
334
335 protected void startWebServer(final MockWebConnection mockConnection) throws Exception {
336 if (STATIC_SERVER_ == null) {
337 final Server server = buildServer(PORT);
338
339 final WebAppContext context = new WebAppContext();
340 context.setContextPath("/");
341 context.setResourceBase("./");
342
343 if (isBasicAuthentication()) {
344 final Constraint constraint = new Constraint();
345 constraint.setName(Constraint.__BASIC_AUTH);
346 constraint.setRoles(new String[]{"user"});
347 constraint.setAuthenticate(true);
348
349 final ConstraintMapping constraintMapping = new ConstraintMapping();
350 constraintMapping.setConstraint(constraint);
351 constraintMapping.setPathSpec("/*");
352
353 final ConstraintSecurityHandler handler = (ConstraintSecurityHandler) context.getSecurityHandler();
354 handler.setLoginService(new HashLoginService("MyRealm", "./src/test/resources/realm.properties"));
355 handler.setAuthMethod(Constraint.__BASIC_AUTH);
356 handler.setConstraintMappings(new ConstraintMapping[]{constraintMapping});
357 }
358
359 context.addServlet(MockWebConnectionServlet.class, "/*");
360 server.setHandler(context);
361
362 if (isHttps()) {
363 final SslConnectionFactory sslConnectionFactory = getSslConnectionFactory();
364
365 final HttpConfiguration sslConfiguration = new HttpConfiguration();
366 sslConfiguration.addCustomizer(new SecureRequestCustomizer());
367 final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(sslConfiguration);
368
369 final ServerConnector connector =
370 new ServerConnector(server, sslConnectionFactory, httpConnectionFactory);
371 connector.setPort(PORT2);
372 server.addConnector(connector);
373 }
374
375 tryStart(PORT, server);
376 STATIC_SERVER_ = server;
377 }
378 MockWebConnectionServlet.setMockconnection(mockConnection);
379 }
380
381
382
383
384
385
386
387 public static void tryStart(final int port, final Server server) throws Exception {
388 final long maxWait = System.currentTimeMillis() + BIND_TIMEOUT;
389
390 while (true) {
391 try {
392 server.start();
393 return;
394 }
395 catch (final BindException e) {
396 if (System.currentTimeMillis() > maxWait) {
397
398 server.stop();
399 server.destroy();
400
401 throw (BindException) new BindException("Port " + port + " is already in use").initCause(e);
402 }
403 Thread.sleep(200);
404 }
405 catch (final IOException e) {
406
407 final Throwable cause = e.getCause();
408 if (cause != null && cause instanceof BindException) {
409 if (System.currentTimeMillis() > maxWait) {
410
411 server.stop();
412 server.destroy();
413
414 throw (BindException) new BindException("Port " + port + " is already in use").initCause(e);
415 }
416 Thread.sleep(200);
417 }
418 else {
419
420 server.stop();
421 server.destroy();
422
423 throw e;
424 }
425 }
426 }
427 }
428
429
430
431
432
433 protected static void stopWebServer() throws Exception {
434 if (STATIC_SERVER_ != null) {
435 STATIC_SERVER_.stop();
436 STATIC_SERVER_.destroy();
437 STATIC_SERVER_ = null;
438 }
439 }
440
441
442
443
444
445
446
447
448 protected final HtmlPage loadPageWithAlerts(final URL url) throws Exception {
449 alertHandler_.clear();
450 expandExpectedAlertsVariables(url);
451 final String[] expectedAlerts = getExpectedAlerts();
452
453 startWebServer(getMockWebConnection());
454
455 final HtmlPage page = getWebClient().getPage(url);
456
457 assertEquals(expectedAlerts, getCollectedAlerts(page));
458 return page;
459 }
460
461
462
463
464
465
466 protected List<String> getCollectedAlerts(final HtmlPage page) {
467 return alertHandler_.getCollectedAlerts();
468 }
469
470
471
472
473
474
475 protected boolean isBasicAuthentication() {
476 return false;
477 }
478
479
480
481
482 protected boolean isHttps() {
483 return false;
484 }
485
486
487
488
489 protected SslConnectionFactory getSslConnectionFactory() {
490 return null;
491 }
492
493
494
495
496
497 protected WebClient getWebClient() {
498 if (webClient_ == null) {
499 webClient_ = new WebClient(getBrowserVersion());
500 webClient_.setAlertHandler(alertHandler_);
501 }
502 return webClient_;
503 }
504
505
506
507
508 @Override
509 @After
510 public void releaseResources() {
511 super.releaseResources();
512 if (webClient_ != null) {
513 webClient_.close();
514 webClient_.getCookieManager().clearCookies();
515 }
516 webClient_ = null;
517 alertHandler_ = null;
518 }
519
520 private static Server buildServer(final int port) {
521 final QueuedThreadPool threadPool = new QueuedThreadPool(10, 2);
522
523 final Server server = new Server(threadPool);
524
525 final ServerConnector connector = new ServerConnector(server, 1, -1, new HttpConnectionFactory());
526 connector.setPort(port);
527 server.setConnectors(new Connector[] {connector});
528
529 return server;
530 }
531 }