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
166 public void doReport(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
167 List<JavaInformations> javaInformationsList) throws IOException, ServletException {
168 assert httpRequest != null;
169 assert httpResponse != null;
170 assert javaInformationsList != null;
171
172 final String resource = HttpParameter.RESOURCE.getParameterFrom(httpRequest);
173 if (resource != null) {
174 doResource(httpResponse, resource);
175 return;
176 }
177
178
179
180 noCache(httpResponse);
181
182 try {
183
184 I18N.bindLocale(httpRequest.getLocale());
185
186 SessionListener.bindSession(httpRequest.getSession(false));
187
188 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
189 final String graph = HttpParameter.GRAPH.getParameterFrom(httpRequest);
190 if (part == null && graph != null) {
191 final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
192 doGraph(httpRequest, httpResponse, range, graph);
193 } else if (HttpPart.WEB_XML.isPart(httpRequest)) {
194 doWebXml(httpResponse);
195 } else if (HttpPart.POM_XML.isPart(httpRequest)) {
196 doPomXml(httpResponse);
197 } else if (HttpPart.JNLP.isPart(httpRequest)) {
198 final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
199 doJnlp(httpRequest, httpResponse, range);
200 } else if (HttpPart.CRASHES.isPart(httpRequest)
201 && HttpParameter.PATH.getParameterFrom(httpRequest) != null) {
202 final String path = HttpParameter.PATH.getParameterFrom(httpRequest);
203 doHsErrPid(httpResponse, javaInformationsList, path);
204 } else if (HttpParameter.REPORT.getParameterFrom(httpRequest) != null) {
205 final String reportName = URLDecoder
206 .decode(HttpParameter.REPORT.getParameterFrom(httpRequest), "UTF-8");
207 doCustomReport(httpRequest, httpResponse, reportName);
208 } else {
209 doReportCore(httpRequest, httpResponse, javaInformationsList);
210 }
211 } finally {
212 I18N.unbindLocale();
213 SessionListener.unbindSession();
214 }
215 }
216
217 private void doReportCore(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
218 List<JavaInformations> javaInformationsList) throws IOException {
219 final String format = HttpParameter.FORMAT.getParameterFrom(httpRequest);
220 if (HttpPart.LAST_VALUE.isPart(httpRequest)
221 && !TransportFormat.isATransportFormat(format)) {
222 doLastValue(httpResponse, HttpParameter.GRAPH.getParameterFrom(httpRequest));
223 } else if (HttpParameter.JMX_VALUE.getParameterFrom(httpRequest) != null
224 && !TransportFormat.isATransportFormat(format)) {
225
226 Action.checkSystemActionsEnabled();
227 doJmxValue(httpResponse, HttpParameter.JMX_VALUE.getParameterFrom(httpRequest));
228 } else if (format == null || "html".equalsIgnoreCase(format)
229 || HtmlController.HTML_BODY_FORMAT.equalsIgnoreCase(format)) {
230 doCompressedHtml(httpRequest, httpResponse, javaInformationsList);
231 } else if ("pdf".equalsIgnoreCase(format)) {
232 final PdfController pdfController = new PdfController(collector, collectorServer);
233 pdfController.doPdf(httpRequest, httpResponse, javaInformationsList);
234 } else if ("prometheus".equalsIgnoreCase(format)) {
235 final boolean includeLastValue = Boolean
236 .parseBoolean(httpRequest.getParameter("includeLastValue"));
237 doPrometheus(httpResponse, javaInformationsList, includeLastValue);
238 } else {
239 doCompressedSerializable(httpRequest, httpResponse, javaInformationsList);
240 }
241 }
242
243 public void doPrometheus(HttpServletResponse httpResponse,
244 List<JavaInformations> javaInformationsList, final boolean includeLastValue)
245 throws IOException {
246 httpResponse.setContentType("text/plain; version=0.0.4;charset=UTF-8");
247 final PrometheusController prometheusController = new PrometheusController(
248 javaInformationsList, collector, httpResponse.getWriter());
249 prometheusController.report(includeLastValue);
250 }
251
252 public static void noCache(HttpServletResponse httpResponse) {
253 httpResponse.addHeader("Cache-Control", "no-cache");
254 httpResponse.addHeader("Pragma", "no-cache");
255 httpResponse.addHeader("Expires", "-1");
256 }
257
258 public void addPdfContentTypeAndDisposition(HttpServletRequest httpRequest,
259 HttpServletResponse httpResponse) {
260
261 new PdfController(collector, collectorServer).addPdfContentTypeAndDisposition(httpRequest,
262 httpResponse);
263 }
264
265 private void doCompressedHtml(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
266 List<JavaInformations> javaInformationsList) throws IOException {
267 if (CSRF_PROTECTION_ENABLED && SessionListener.getCurrentSession() == null) {
268 SessionListener.bindSession(httpRequest.getSession());
269 }
270 final HtmlController htmlController = new HtmlController(collector, collectorServer,
271 messageForReport, anchorNameForRedirect);
272 if (isCompressionSupported(httpRequest, httpResponse)) {
273
274
275
276
277 final CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
278 httpResponse, 4096);
279 try {
280 htmlController.doHtml(httpRequest, wrappedResponse, javaInformationsList);
281 } finally {
282 wrappedResponse.finishResponse();
283 }
284 } else {
285 htmlController.doHtml(httpRequest, httpResponse, javaInformationsList);
286 }
287 }
288
289 public void writeHtmlToLastShutdownFile() {
290 new HtmlController(collector, collectorServer, messageForReport, anchorNameForRedirect)
291 .writeHtmlToLastShutdownFile();
292 }
293
294 static BufferedWriter getWriter(HttpServletResponse httpResponse) throws IOException {
295 return HtmlController.getWriter(httpResponse);
296 }
297
298 private void doCompressedSerializable(HttpServletRequest httpRequest,
299 HttpServletResponse httpResponse, List<JavaInformations> javaInformationsList)
300 throws IOException {
301 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
302 if (HtmlController.isLocalCollectNeeded(part)
303 && HttpParameter.PERIOD.getParameterFrom(httpRequest) != null) {
304
305
306 collector.collectLocalContextWithoutErrors();
307 }
308 Serializable serializable;
309 try {
310 final SerializableController serializableController = new SerializableController(
311 collector);
312 serializable = serializableController.createSerializable(httpRequest,
313 javaInformationsList, messageForReport);
314 } catch (final Throwable t) {
315 serializable = t;
316 }
317 doCompressedSerializable(httpRequest, httpResponse, serializable);
318 }
319
320 public void doCompressedSerializable(HttpServletRequest httpRequest,
321 HttpServletResponse httpResponse, Serializable serializable) throws IOException {
322
323
324 final SerializableController serializableController = new SerializableController(collector);
325 if (isCompressionSupported(httpRequest, httpResponse)) {
326
327
328
329
330 final CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
331 httpResponse, 50 * 1024);
332 try {
333 serializableController.doSerializable(httpRequest, wrappedResponse, serializable);
334 } finally {
335 wrappedResponse.finishResponse();
336 }
337 } else {
338 serializableController.doSerializable(httpRequest, httpResponse, serializable);
339 }
340 }
341
342 public static void doResource(HttpServletResponse httpResponse, String resource)
343 throws IOException {
344
345 final String localResource = Parameters.getResourcePath(resource.replace("..", ""));
346 final InputStream resourceAsStream = MonitoringController.class
347 .getResourceAsStream(localResource);
348 if (resourceAsStream == null) {
349 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
350 return;
351 }
352 try {
353 addHeadersForResource(httpResponse, localResource);
354
355 final OutputStream out = httpResponse.getOutputStream();
356 InputOutput.pump(resourceAsStream, out);
357 } finally {
358 resourceAsStream.close();
359 }
360 }
361
362 public static void addHeadersForResource(HttpServletResponse httpResponse, String resource) {
363 httpResponse.addHeader("Cache-Control", "max-age=3600");
364
365
366 if (resource.endsWith(".css")) {
367 httpResponse.setContentType("text/css");
368 } else {
369 final String mimeType = Parameters.getServletContext().getMimeType(resource);
370
371 if (mimeType != null) {
372 httpResponse.setContentType(mimeType);
373 }
374 }
375 }
376
377 private void doGraph(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
378 Range range, String graphName) throws IOException {
379 final JRobin jrobin = collector.getJRobin(graphName);
380 if (jrobin != null) {
381 final String format = HttpParameter.FORMAT.getParameterFrom(httpRequest);
382 if ("xml".equals(format)) {
383
384 httpResponse.setContentType("text/xml; charset=UTF-8");
385 if (isCompressionSupported(httpRequest, httpResponse)) {
386 final CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
387 httpResponse, 4096);
388 try {
389 jrobin.dumpXml(wrappedResponse.getOutputStream(), range);
390 } finally {
391 wrappedResponse.finishResponse();
392 }
393 } else {
394 jrobin.dumpXml(httpResponse.getOutputStream(), range);
395 }
396 } else if ("txt".equals(format)) {
397
398 httpResponse.setContentType("text/plain; charset=UTF-8");
399 final String txt = jrobin.dumpTxt(range);
400 httpResponse.setContentLength(txt.length());
401 httpResponse.getWriter().write(txt);
402 } else {
403 final int width = Math.min(
404 Integer.parseInt(HttpParameter.WIDTH.getParameterFrom(httpRequest)), 1600);
405 final int height = Math.min(
406 Integer.parseInt(HttpParameter.HEIGHT.getParameterFrom(httpRequest)), 1600);
407 final String max = HttpParameter.MAX.getParameterFrom(httpRequest);
408 final boolean maxHidden = max != null && !Boolean.parseBoolean(max);
409 final byte[] img = jrobin.graph(range, width, height, maxHidden);
410
411 httpResponse.setContentType("image/png");
412 httpResponse.setContentLength(img.length);
413 final String fileName = graphName + ".png";
414
415 httpResponse.addHeader("Content-Disposition",
416 "inline;filename=" + fileName.replace('\n', '_').replace('\r', '_'));
417 httpResponse.getOutputStream().write(img);
418 httpResponse.flushBuffer();
419 }
420 }
421 }
422
423
424 private void doLastValue(HttpServletResponse httpResponse, String graphName)
425 throws IOException {
426 httpResponse.setContentType("text/plain");
427 boolean first = true;
428 for (final String graph : graphName.split(",")) {
429 final JRobin jrobin = collector.getJRobin(graph);
430 final double lastValue;
431 if (jrobin == null) {
432 lastValue = -1;
433 } else {
434 lastValue = jrobin.getLastValue();
435 }
436 if (first) {
437 first = false;
438 } else {
439 httpResponse.getWriter().write(",");
440 }
441 httpResponse.getWriter().write(String.valueOf(lastValue));
442 }
443 httpResponse.flushBuffer();
444 }
445
446
447 private void doJmxValue(HttpServletResponse httpResponse, String jmxValueParameter)
448 throws IOException {
449 httpResponse.setContentType("text/plain");
450 httpResponse.getWriter().write(MBeans.getConvertedAttributes(jmxValueParameter));
451 httpResponse.flushBuffer();
452 }
453
454 private void doWebXml(HttpServletResponse httpResponse) throws IOException {
455
456 Action.checkSystemActionsEnabled();
457 final OutputStream out = httpResponse.getOutputStream();
458 httpResponse.setContentType("application/xml");
459 httpResponse.addHeader("Content-Disposition", "inline;filename=web.xml");
460 final InputStream in = getWebXmlAsStream();
461 if (in != null) {
462 try {
463 InputOutput.pump(in, out);
464 } finally {
465 in.close();
466 }
467 }
468 }
469
470 private void doPomXml(HttpServletResponse httpResponse) throws IOException {
471
472 Action.checkSystemActionsEnabled();
473 final OutputStream out = httpResponse.getOutputStream();
474 httpResponse.setContentType("application/xml");
475 httpResponse.addHeader("Content-Disposition", "inline;filename=pom.xml");
476 final InputStream in = MavenArtifact.getWebappPomXmlAsStream();
477 if (in != null) {
478 try {
479 InputOutput.pump(in, out);
480 } finally {
481 in.close();
482 }
483 }
484 }
485
486 private static InputStream getWebXmlAsStream() {
487 final InputStream webXml = Parameters.getServletContext()
488 .getResourceAsStream("/WEB-INF/web.xml");
489 if (webXml == null) {
490 return null;
491 }
492 return new BufferedInputStream(webXml);
493 }
494
495 private void doJnlp(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
496 Range range) throws IOException {
497 httpResponse.setContentType("application/x-java-jnlp-file");
498 final String codebase = httpRequest.getRequestURL().toString();
499 final String cookies = httpCookieManager.getCookiesAsString(httpRequest);
500
501 new JnlpPage(collector, collectorServer, codebase, cookies, range, httpResponse.getWriter())
502 .toJnlp();
503 }
504
505 private void doHsErrPid(HttpServletResponse httpResponse,
506 List<JavaInformations> javaInformationsList, String path) throws IOException {
507 for (final JavaInformations javaInformations : javaInformationsList) {
508 for (final HsErrPid hsErrPid : javaInformations.getHsErrPidList()) {
509 if (hsErrPid.getFile().replace('\\', '/').equals(path) && new File(path).exists()) {
510 final File file = new File(path);
511 final OutputStream out = httpResponse.getOutputStream();
512 httpResponse.setContentType("text/plain");
513
514 httpResponse.addHeader("Content-Disposition",
515 "attachment;filename=" + file.getName());
516 InputOutput.pumpFromFile(file, out);
517 return;
518 }
519 }
520 }
521
522
523 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
524 }
525
526 private static void doCustomReport(HttpServletRequest httpRequest,
527 HttpServletResponse httpResponse, String reportName)
528 throws ServletException, IOException {
529 final String customReportPath = Parameters.getParameterValueByName(reportName);
530 if (!customReportPath.isEmpty() && customReportPath.charAt(0) == '/'
531 && Parameters.getServletContext().getRequestDispatcher(customReportPath) != null) {
532 Parameters.getServletContext().getRequestDispatcher(customReportPath)
533 .forward(httpRequest, httpResponse);
534 } else {
535 httpResponse.sendRedirect(customReportPath);
536 }
537 }
538
539 static boolean isCompressionSupported(HttpServletRequest httpRequest,
540 HttpServletResponse httpResponse) {
541
542
543
544
545
546
547 if (GZIP_COMPRESSION_DISABLED
548 || httpResponse instanceof CompressionServletResponseWrapper) {
549 return false;
550 }
551
552 boolean supportCompression = false;
553 final List<String> acceptEncodings = Collections
554 .list(httpRequest.getHeaders("Accept-Encoding"));
555 for (final String name : acceptEncodings) {
556 if (name.contains("gzip")) {
557 supportCompression = true;
558 break;
559 }
560 }
561 return supportCompression;
562 }
563
564 public static boolean isJavaInformationsNeeded(HttpServletRequest httpRequest) {
565 if (HttpParameter.RESOURCE.getParameterFrom(httpRequest) == null
566 && HttpParameter.GRAPH.getParameterFrom(httpRequest) == null) {
567 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
568 return part == null || HttpPart.CURRENT_REQUESTS.getName().equals(part)
569 || HttpPart.DEFAULT_WITH_CURRENT_REQUESTS.getName().equals(part)
570 || HttpPart.JVM.getName().equals(part)
571 || HttpPart.THREADS.getName().equals(part)
572 || HttpPart.THREADS_DUMP.getName().equals(part)
573 || HttpPart.CRASHES.getName().equals(part);
574 }
575 return false;
576 }
577 }
578