1
18 package net.bull.javamelody.internal.web;
19
20 import java.io.BufferedInputStream;
21 import java.io.BufferedWriter;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.io.Serializable;
27 import java.net.URLDecoder;
28 import java.util.Collections;
29 import java.util.List;
30
31 import javax.servlet.ServletContext;
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 import javax.servlet.http.HttpSession;
36
37 import net.bull.javamelody.Parameter;
38 import net.bull.javamelody.SessionListener;
39 import net.bull.javamelody.internal.common.HttpParameter;
40 import net.bull.javamelody.internal.common.HttpPart;
41 import net.bull.javamelody.internal.common.I18N;
42 import net.bull.javamelody.internal.common.InputOutput;
43 import net.bull.javamelody.internal.common.LOG;
44 import net.bull.javamelody.internal.common.Parameters;
45 import net.bull.javamelody.internal.model.Action;
46 import net.bull.javamelody.internal.model.Collector;
47 import net.bull.javamelody.internal.model.CollectorServer;
48 import net.bull.javamelody.internal.model.HsErrPid;
49 import net.bull.javamelody.internal.model.JRobin;
50 import net.bull.javamelody.internal.model.JavaInformations;
51 import net.bull.javamelody.internal.model.MBeans;
52 import net.bull.javamelody.internal.model.MavenArtifact;
53 import net.bull.javamelody.internal.model.Range;
54 import net.bull.javamelody.internal.model.TransportFormat;
55
56
60 public class MonitoringController {
61 static {
62 boolean webXmlExists = false;
63 boolean pomXmlExists = false;
64 try {
65 final InputStream webXmlAsStream = getWebXmlAsStream();
66 if (webXmlAsStream != null) {
67 webXmlAsStream.close();
68 webXmlExists = true;
69 }
70 final InputStream pomXmlAsStream = MavenArtifact.getWebappPomXmlAsStream();
71 if (pomXmlAsStream != null) {
72 pomXmlAsStream.close();
73 pomXmlExists = true;
74 }
75 } catch (final IOException e) {
76 LOG.warn(e.toString(), e);
77 }
78 JavaInformations.setWebXmlExistsAndPomXmlExists(webXmlExists, pomXmlExists);
79 }
80
81 private static final boolean GZIP_COMPRESSION_DISABLED = Parameter.GZIP_COMPRESSION_DISABLED
82 .getValueAsBoolean();
83 private static final boolean CSRF_PROTECTION_ENABLED = Parameter.CSRF_PROTECTION_ENABLED
84 .getValueAsBoolean();
85
86 private final HttpCookieManager httpCookieManager = new HttpCookieManager();
87 private final Collector collector;
88 private final CollectorServer collectorServer;
89 private String messageForReport;
90 private String anchorNameForRedirect;
91
92 public MonitoringController(Collector collector, CollectorServer collectorServer) {
93 super();
94 assert collector != null;
95 this.collector = collector;
96 this.collectorServer = collectorServer;
97 }
98
99 public String executeActionIfNeeded(HttpServletRequest httpRequest) throws IOException {
100 assert httpRequest != null;
101 final String actionParameter = HttpParameter.ACTION.getParameterFrom(httpRequest);
102 if (actionParameter != null) {
103 if (CSRF_PROTECTION_ENABLED) {
104 checkCsrfToken(httpRequest);
105 }
106 try {
107
108 I18N.bindLocale(httpRequest.getLocale());
109
110 final Action action = Action.valueOfIgnoreCase(actionParameter);
111 if (action != Action.CLEAR_COUNTER && action != Action.MAIL_TEST) {
112 Action.checkSystemActionsEnabled();
113 }
114 final HttpSession currentSession = httpRequest.getSession(false);
115 final String counterName = HttpParameter.COUNTER.getParameterFrom(httpRequest);
116 final String sessionId = HttpParameter.SESSION_ID.getParameterFrom(httpRequest);
117 final String threadId = HttpParameter.THREAD_ID.getParameterFrom(httpRequest);
118 final String jobId = HttpParameter.JOB_ID.getParameterFrom(httpRequest);
119 final String cacheId = HttpParameter.CACHE_ID.getParameterFrom(httpRequest);
120 final String cacheKey = HttpParameter.CACHE_KEY.getParameterFrom(httpRequest);
121 messageForReport = action.execute(collector, collectorServer, currentSession,
122 counterName, sessionId, threadId, jobId, cacheId, cacheKey);
123 if (collector.getCounterByName(counterName) != null) {
124
125 anchorNameForRedirect = action.getContextName(counterName);
126 } else {
127 anchorNameForRedirect = action.getContextName(null);
128 }
129 return messageForReport;
130 } finally {
131 I18N.unbindLocale();
132 }
133 }
134 return null;
135 }
136
137 public static void checkCsrfToken(HttpServletRequest httpRequest) {
138 final String token = HttpParameter.TOKEN.getParameterFrom(httpRequest);
139 if (token == null) {
140 throw new IllegalArgumentException("csrf token missing");
141 }
142 final HttpSession session = httpRequest.getSession(false);
143 if (session == null
144 || !token.equals(session.getAttribute(SessionListener.CSRF_TOKEN_SESSION_NAME))) {
145 throw new IllegalArgumentException("invalid token parameter");
146 }
147 }
148
149 public void doActionIfNeededAndReport(HttpServletRequest httpRequest,
150 HttpServletResponse httpResponse, ServletContext servletContext)
151 throws IOException, ServletException {
152 executeActionIfNeeded(httpRequest);
153
154
155
156 final JavaInformations javaInformations;
157 if (isJavaInformationsNeeded(httpRequest)) {
158 javaInformations = new JavaInformations(servletContext, true);
159 } else {
160 javaInformations = null;
161 }
162
163 doReport(httpRequest, httpResponse, Collections.singletonList(javaInformations));
164
165 httpResponse.flushBuffer();
166 }
167
168 public void doReport(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
169 List<JavaInformations> javaInformationsList) throws IOException, ServletException {
170 assert httpRequest != null;
171 assert httpResponse != null;
172 assert javaInformationsList != null;
173
174 final String resource = HttpParameter.RESOURCE.getParameterFrom(httpRequest);
175 if (resource != null) {
176 doResource(httpResponse, resource);
177 return;
178 }
179
180
181
182 noCache(httpResponse);
183
184 try {
185
186 I18N.bindLocale(httpRequest.getLocale());
187
188 SessionListener.bindSession(httpRequest.getSession(false));
189
190 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
191 final String graph = HttpParameter.GRAPH.getParameterFrom(httpRequest);
192 if (part == null && graph != null) {
193 final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
194 doGraph(httpRequest, httpResponse, range, graph);
195 } else if (HttpPart.WEB_XML.isPart(httpRequest)) {
196 doWebXml(httpResponse);
197 } else if (HttpPart.POM_XML.isPart(httpRequest)) {
198 doPomXml(httpResponse);
199 } else if (HttpPart.JNLP.isPart(httpRequest)) {
200 final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
201 doJnlp(httpRequest, httpResponse, range);
202 } else if (HttpPart.CRASHES.isPart(httpRequest)
203 && HttpParameter.PATH.getParameterFrom(httpRequest) != null) {
204 final String path = HttpParameter.PATH.getParameterFrom(httpRequest);
205 doHsErrPid(httpResponse, javaInformationsList, path);
206 } else if (HttpParameter.REPORT.getParameterFrom(httpRequest) != null) {
207 final String reportName = URLDecoder
208 .decode(HttpParameter.REPORT.getParameterFrom(httpRequest), "UTF-8");
209 doCustomReport(httpRequest, httpResponse, reportName);
210 } else {
211 doReportCore(httpRequest, httpResponse, javaInformationsList);
212 }
213 } finally {
214 I18N.unbindLocale();
215 SessionListener.unbindSession();
216 }
217 }
218
219 private void doReportCore(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
220 List<JavaInformations> javaInformationsList) throws IOException {
221 final String format = HttpParameter.FORMAT.getParameterFrom(httpRequest);
222 if (HttpPart.LAST_VALUE.isPart(httpRequest)
223 && !TransportFormat.isATransportFormat(format)) {
224 doLastValue(httpResponse, HttpParameter.GRAPH.getParameterFrom(httpRequest));
225 } else if (HttpParameter.JMX_VALUE.getParameterFrom(httpRequest) != null
226 && !TransportFormat.isATransportFormat(format)) {
227
228 Action.checkSystemActionsEnabled();
229 doJmxValue(httpResponse, HttpParameter.JMX_VALUE.getParameterFrom(httpRequest));
230 } else if (format == null || "html".equalsIgnoreCase(format)
231 || HtmlController.HTML_BODY_FORMAT.equalsIgnoreCase(format)) {
232 doCompressedHtml(httpRequest, httpResponse, javaInformationsList);
233 } else if ("pdf".equalsIgnoreCase(format)) {
234 final PdfController pdfController = new PdfController(collector, collectorServer);
235 pdfController.doPdf(httpRequest, httpResponse, javaInformationsList);
236 } else if ("prometheus".equalsIgnoreCase(format)) {
237 final boolean includeLastValue = Boolean
238 .parseBoolean(httpRequest.getParameter("includeLastValue"));
239 doPrometheus(httpResponse, javaInformationsList, includeLastValue);
240 } else {
241 doCompressedSerializable(httpRequest, httpResponse, javaInformationsList);
242 }
243 }
244
245 public void doPrometheus(HttpServletResponse httpResponse,
246 List<JavaInformations> javaInformationsList, final boolean includeLastValue)
247 throws IOException {
248 httpResponse.setContentType("text/plain; version=0.0.4;charset=UTF-8");
249 final PrometheusController prometheusController = new PrometheusController(
250 javaInformationsList, collector, httpResponse.getWriter());
251 prometheusController.report(includeLastValue);
252
253 httpResponse.getWriter().flush();
254 }
255
256 public static void noCache(HttpServletResponse httpResponse) {
257 httpResponse.addHeader("Cache-Control", "no-cache");
258 httpResponse.addHeader("Pragma", "no-cache");
259 httpResponse.addHeader("Expires", "-1");
260 }
261
262 public void addPdfContentTypeAndDisposition(HttpServletRequest httpRequest,
263 HttpServletResponse httpResponse) {
264
265 new PdfController(collector, collectorServer).addPdfContentTypeAndDisposition(httpRequest,
266 httpResponse);
267 }
268
269 private void doCompressedHtml(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
270 List<JavaInformations> javaInformationsList) throws IOException {
271 if (CSRF_PROTECTION_ENABLED && SessionListener.getCurrentSession() == null) {
272 SessionListener.bindSession(httpRequest.getSession());
273 }
274 final HtmlController htmlController = new HtmlController(collector, collectorServer,
275 messageForReport, anchorNameForRedirect);
276 if (isCompressionSupported(httpRequest, httpResponse)) {
277
278
279
280
281 final CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
282 httpResponse, 4096);
283 try {
284 htmlController.doHtml(httpRequest, wrappedResponse, javaInformationsList);
285 } finally {
286 wrappedResponse.finishResponse();
287 }
288 } else {
289 htmlController.doHtml(httpRequest, httpResponse, javaInformationsList);
290 }
291 }
292
293 public void writeHtmlToLastShutdownFile() {
294 new HtmlController(collector, collectorServer, messageForReport, anchorNameForRedirect)
295 .writeHtmlToLastShutdownFile();
296 }
297
298 static BufferedWriter getWriter(HttpServletResponse httpResponse) throws IOException {
299 return HtmlController.getWriter(httpResponse);
300 }
301
302 private void doCompressedSerializable(HttpServletRequest httpRequest,
303 HttpServletResponse httpResponse, List<JavaInformations> javaInformationsList)
304 throws IOException {
305 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
306 if (HtmlController.isLocalCollectNeeded(part)
307 && HttpParameter.PERIOD.getParameterFrom(httpRequest) != null) {
308
309
310 collector.collectLocalContextWithoutErrors();
311 }
312 Serializable serializable;
313 try {
314 final SerializableController serializableController = new SerializableController(
315 collector);
316 serializable = serializableController.createSerializable(httpRequest,
317 javaInformationsList, messageForReport);
318 } catch (final Throwable t) {
319 serializable = t;
320 }
321 doCompressedSerializable(httpRequest, httpResponse, serializable);
322 }
323
324 public void doCompressedSerializable(HttpServletRequest httpRequest,
325 HttpServletResponse httpResponse, Serializable serializable) throws IOException {
326
327
328 final SerializableController serializableController = new SerializableController(collector);
329 if (isCompressionSupported(httpRequest, httpResponse)) {
330
331
332
333
334 final CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
335 httpResponse, 50 * 1024);
336 try {
337 serializableController.doSerializable(httpRequest, wrappedResponse, serializable);
338 } finally {
339 wrappedResponse.finishResponse();
340 }
341 } else {
342 serializableController.doSerializable(httpRequest, httpResponse, serializable);
343 }
344 }
345
346 public static void doResource(HttpServletResponse httpResponse, String resource)
347 throws IOException {
348
349 final String localResource = Parameters.getResourcePath(resource.replace("..", ""));
350 final InputStream resourceAsStream = MonitoringController.class
351 .getResourceAsStream(localResource);
352 if (resourceAsStream == null) {
353 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
354 return;
355 }
356 try {
357 addHeadersForResource(httpResponse, localResource);
358
359 final OutputStream out = httpResponse.getOutputStream();
360 InputOutput.pump(resourceAsStream, out);
361 } finally {
362 resourceAsStream.close();
363 }
364 }
365
366 public static void addHeadersForResource(HttpServletResponse httpResponse, String resource) {
367 httpResponse.addHeader("Cache-Control", "max-age=3600");
368
369
370 if (resource.endsWith(".css")) {
371 httpResponse.setContentType("text/css");
372 } else {
373 final String mimeType = Parameters.getServletContext().getMimeType(resource);
374
375 if (mimeType != null) {
376 httpResponse.setContentType(mimeType);
377 }
378 }
379 }
380
381 private void doGraph(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
382 Range range, String graphName) throws IOException {
383 final JRobin jrobin = collector.getJRobin(graphName);
384 if (jrobin != null) {
385 final String format = HttpParameter.FORMAT.getParameterFrom(httpRequest);
386 if ("xml".equals(format)) {
387
388 httpResponse.setContentType("text/xml; charset=UTF-8");
389 if (isCompressionSupported(httpRequest, httpResponse)) {
390 final CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
391 httpResponse, 4096);
392 try {
393 jrobin.dumpXml(wrappedResponse.getOutputStream(), range);
394 } finally {
395 wrappedResponse.finishResponse();
396 }
397 } else {
398 jrobin.dumpXml(httpResponse.getOutputStream(), range);
399 }
400 } else if ("txt".equals(format)) {
401
402 httpResponse.setContentType("text/plain; charset=UTF-8");
403 final String txt = jrobin.dumpTxt(range);
404 httpResponse.setContentLength(txt.length());
405 httpResponse.getWriter().write(txt);
406 } else {
407 final int width = Math.min(
408 Integer.parseInt(HttpParameter.WIDTH.getParameterFrom(httpRequest)), 1600);
409 final int height = Math.min(
410 Integer.parseInt(HttpParameter.HEIGHT.getParameterFrom(httpRequest)), 1600);
411 final String max = HttpParameter.MAX.getParameterFrom(httpRequest);
412 final boolean maxHidden = max != null && !Boolean.parseBoolean(max);
413 final byte[] img = jrobin.graph(range, width, height, maxHidden);
414
415 httpResponse.setContentType("image/png");
416 httpResponse.setContentLength(img.length);
417 final String fileName = graphName + ".png";
418
419 httpResponse.addHeader("Content-Disposition",
420 "inline;filename=" + fileName.replace('\n', '_').replace('\r', '_'));
421 httpResponse.getOutputStream().write(img);
422 httpResponse.flushBuffer();
423 }
424 }
425 }
426
427
428 private void doLastValue(HttpServletResponse httpResponse, String graphName)
429 throws IOException {
430 httpResponse.setContentType("text/plain");
431 boolean first = true;
432 for (final String graph : graphName.split(",")) {
433 final JRobin jrobin = collector.getJRobin(graph);
434 final double lastValue;
435 if (jrobin == null) {
436 lastValue = -1;
437 } else {
438 lastValue = jrobin.getLastValue();
439 }
440 if (first) {
441 first = false;
442 } else {
443 httpResponse.getWriter().write(",");
444 }
445 httpResponse.getWriter().write(String.valueOf(lastValue));
446 }
447 httpResponse.flushBuffer();
448 }
449
450
451 private void doJmxValue(HttpServletResponse httpResponse, String jmxValueParameter)
452 throws IOException {
453 httpResponse.setContentType("text/plain");
454 httpResponse.getWriter().write(MBeans.getConvertedAttributes(jmxValueParameter));
455 httpResponse.flushBuffer();
456 }
457
458 private void doWebXml(HttpServletResponse httpResponse) throws IOException {
459
460 Action.checkSystemActionsEnabled();
461 final OutputStream out = httpResponse.getOutputStream();
462 httpResponse.setContentType("application/xml");
463 httpResponse.addHeader("Content-Disposition", "inline;filename=web.xml");
464 final InputStream in = getWebXmlAsStream();
465 if (in != null) {
466 try {
467 InputOutput.pump(in, out);
468 } finally {
469 in.close();
470 }
471 }
472 }
473
474 private void doPomXml(HttpServletResponse httpResponse) throws IOException {
475
476 Action.checkSystemActionsEnabled();
477 final OutputStream out = httpResponse.getOutputStream();
478 httpResponse.setContentType("application/xml");
479 httpResponse.addHeader("Content-Disposition", "inline;filename=pom.xml");
480 final InputStream in = MavenArtifact.getWebappPomXmlAsStream();
481 if (in != null) {
482 try {
483 InputOutput.pump(in, out);
484 } finally {
485 in.close();
486 }
487 }
488 }
489
490 private static InputStream getWebXmlAsStream() {
491 final InputStream webXml = Parameters.getServletContext()
492 .getResourceAsStream("/WEB-INF/web.xml");
493 if (webXml == null) {
494 return null;
495 }
496 return new BufferedInputStream(webXml);
497 }
498
499 private void doJnlp(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
500 Range range) throws IOException {
501 httpResponse.setContentType("application/x-java-jnlp-file");
502 final String codebase = httpRequest.getRequestURL().toString();
503 final String cookies = httpCookieManager.getCookiesAsString(httpRequest);
504
505 new JnlpPage(collector, collectorServer, codebase, cookies, range, httpResponse.getWriter())
506 .toJnlp();
507 }
508
509 private void doHsErrPid(HttpServletResponse httpResponse,
510 List<JavaInformations> javaInformationsList, String path) throws IOException {
511 for (final JavaInformations javaInformations : javaInformationsList) {
512 for (final HsErrPid hsErrPid : javaInformations.getHsErrPidList()) {
513 if (hsErrPid.getFile().replace('\\', '/').equals(path) && new File(path).exists()) {
514 final File file = new File(path);
515 final OutputStream out = httpResponse.getOutputStream();
516 httpResponse.setContentType("text/plain");
517
518 httpResponse.addHeader("Content-Disposition",
519 "attachment;filename=" + file.getName());
520 InputOutput.pumpFromFile(file, out);
521 return;
522 }
523 }
524 }
525
526
527 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
528 }
529
530 private static void doCustomReport(HttpServletRequest httpRequest,
531 HttpServletResponse httpResponse, String reportName)
532 throws ServletException, IOException {
533 final String customReportPath = Parameters.getParameterValueByName(reportName);
534 if (!customReportPath.isEmpty() && customReportPath.charAt(0) == '/'
535 && Parameters.getServletContext().getRequestDispatcher(customReportPath) != null) {
536 Parameters.getServletContext().getRequestDispatcher(customReportPath)
537 .forward(httpRequest, httpResponse);
538 } else {
539 httpResponse.sendRedirect(customReportPath);
540 }
541 }
542
543 static boolean isCompressionSupported(HttpServletRequest httpRequest,
544 HttpServletResponse httpResponse) {
545
546
547
548
549
550
551 if (GZIP_COMPRESSION_DISABLED
552 || httpResponse instanceof CompressionServletResponseWrapper) {
553 return false;
554 }
555
556 boolean supportCompression = false;
557 final List<String> acceptEncodings = Collections
558 .list(httpRequest.getHeaders("Accept-Encoding"));
559 for (final String name : acceptEncodings) {
560 if (name.contains("gzip")) {
561 supportCompression = true;
562 break;
563 }
564 }
565 return supportCompression;
566 }
567
568 public static boolean isJavaInformationsNeeded(HttpServletRequest httpRequest) {
569 if (HttpParameter.RESOURCE.getParameterFrom(httpRequest) == null
570 && HttpParameter.GRAPH.getParameterFrom(httpRequest) == null) {
571 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
572 return part == null || HttpPart.CURRENT_REQUESTS.getName().equals(part)
573 || HttpPart.DEFAULT_WITH_CURRENT_REQUESTS.getName().equals(part)
574 || HttpPart.JVM.getName().equals(part)
575 || HttpPart.THREADS.getName().equals(part)
576 || HttpPart.THREADS_DUMP.getName().equals(part)
577 || HttpPart.CRASHES.getName().equals(part);
578 }
579 return false;
580 }
581 }
582