1
18 package net.bull.javamelody.internal.model;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.Serializable;
25 import java.net.HttpURLConnection;
26 import java.net.URL;
27 import java.net.URLConnection;
28 import java.nio.charset.StandardCharsets;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Map;
32 import java.util.zip.GZIPInputStream;
33
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36
37 import net.bull.javamelody.internal.common.HttpParameter;
38 import net.bull.javamelody.internal.common.HttpPart;
39 import net.bull.javamelody.internal.common.I18N;
40 import net.bull.javamelody.internal.common.InputOutput;
41 import net.bull.javamelody.internal.common.LOG;
42 import net.bull.javamelody.internal.common.Parameters;
43
44
49 public class LabradorRetriever {
50
51 private static final int CONNECTION_TIMEOUT = 20000;
52
53
54 private static final int READ_TIMEOUT = 60000;
55
56 private final URL url;
57 private final Map<String, String> headers;
58
59
60
61
62
63 private static class CounterInputStream extends InputStream {
64 private final InputStream inputStream;
65 private int dataLength;
66
67 CounterInputStream(InputStream inputStream) {
68 super();
69 this.inputStream = inputStream;
70 }
71
72 int getDataLength() {
73 return dataLength;
74 }
75
76 @Override
77 public int read() throws IOException {
78 final int result = inputStream.read();
79 if (result != -1) {
80 dataLength += 1;
81 }
82 return result;
83 }
84
85 @Override
86 public int read(byte[] bytes) throws IOException {
87 final int result = inputStream.read(bytes);
88 if (result != -1) {
89 dataLength += result;
90 }
91 return result;
92 }
93
94 @Override
95 public int read(byte[] bytes, int off, int len) throws IOException {
96 final int result = inputStream.read(bytes, off, len);
97 if (result != -1) {
98 dataLength += result;
99 }
100 return result;
101 }
102
103 @Override
104 public long skip(long n) throws IOException {
105 return inputStream.skip(n);
106 }
107
108 @Override
109 public int available() throws IOException {
110 return inputStream.available();
111 }
112
113 @Override
114 public void close() throws IOException {
115 inputStream.close();
116 }
117
118 @Override
119 public boolean markSupported() {
120 return false;
121 }
122 }
123
124 public LabradorRetriever(URL url) {
125 this(url, null);
126 }
127
128 public LabradorRetriever(URL url, Map<String, String> headers) {
129 super();
130 assert url != null;
131 this.url = url;
132 this.headers = headers;
133 }
134
135 <T> T call() throws IOException {
136 if (shouldMock()) {
137
138 return this.<T> createMockResultOfCall();
139 }
140 final long start = System.currentTimeMillis();
141 int dataLength = -1;
142 try {
143 final URLConnection connection = openConnection();
144
145
146 connection.setRequestProperty("Accept-Language", I18N.getCurrentLocale().getLanguage());
147
148
149
150
151
152 connection.connect();
153
154
155
156
157 final CounterInputStream counterInputStream = new CounterInputStream(
158 connection.getInputStream());
159
160 final T result;
161 try {
162 @SuppressWarnings("unchecked")
163 final T tmp = (T) read(connection, counterInputStream);
164 result = tmp;
165 } finally {
166 counterInputStream.close();
167 dataLength = counterInputStream.getDataLength();
168 }
169 LOG.debug("read on " + url + " : " + result);
170
171 if (result instanceof RuntimeException) {
172 throw (RuntimeException) result;
173 } else if (result instanceof Error) {
174 throw (Error) result;
175 } else if (result instanceof IOException) {
176 throw (IOException) result;
177 } else if (result instanceof Exception) {
178 throw createIOException((Exception) result);
179 }
180 return result;
181 } catch (final ClassNotFoundException e) {
182 throw createIOException(e);
183 } finally {
184 LOG.info("http call done in " + (System.currentTimeMillis() - start) + " ms with "
185 + dataLength / 1024 + " KB read for " + url);
186 }
187 }
188
189 private static IOException createIOException(Exception e) {
190
191 return new IOException(e.getMessage(), e);
192 }
193
194 public void copyTo(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
195 throws IOException {
196 if (shouldMock()) {
197 return;
198 }
199 assert httpRequest != null;
200 assert httpResponse != null;
201 final long start = System.currentTimeMillis();
202 int dataLength = -1;
203 try {
204 final URLConnection connection = openConnection();
205
206 connection.setRequestProperty("Accept-Language",
207 httpRequest.getHeader("Accept-Language"));
208 connection.connect();
209 httpResponse.setContentType(connection.getContentType());
210
211 final String contentDisposition = connection.getHeaderField("Content-Disposition");
212 if (contentDisposition != null) {
213 httpResponse.setHeader("Content-Disposition", contentDisposition);
214 }
215 final OutputStream output = httpResponse.getOutputStream();
216 dataLength = pump(output, connection);
217 } finally {
218 LOG.info("http call done in " + (System.currentTimeMillis() - start) + " ms with "
219 + dataLength / 1024 + " KB read for " + url);
220 }
221 }
222
223 void downloadTo(OutputStream output) throws IOException {
224 if (shouldMock()) {
225 return;
226 }
227 assert output != null;
228 final long start = System.currentTimeMillis();
229 int dataLength = -1;
230 try {
231 final URLConnection connection = openConnection();
232 connection.connect();
233
234 dataLength = pump(output, connection);
235 } finally {
236 LOG.info("http call done in " + (System.currentTimeMillis() - start) + " ms with "
237 + dataLength / 1024 + " KB read for " + url);
238 }
239 }
240
241 private int pump(OutputStream output, URLConnection connection) throws IOException {
242 final int dataLength;
243 final CounterInputStream counterInputStream = new CounterInputStream(
244 connection.getInputStream());
245 InputStream input = counterInputStream;
246 try {
247 if ("gzip".equals(connection.getContentEncoding())) {
248 input = new GZIPInputStream(input);
249 }
250 InputOutput.pump(input, output);
251 } finally {
252 try {
253 input.close();
254 } finally {
255 close(connection);
256 dataLength = counterInputStream.getDataLength();
257 }
258 }
259 return dataLength;
260 }
261
262 public void post(ByteArrayOutputStream payload) throws IOException {
263 final HttpURLConnection connection = (HttpURLConnection) openConnection();
264 connection.setRequestMethod("POST");
265 connection.setDoOutput(true);
266
267 if (payload != null) {
268 final OutputStream outputStream = connection.getOutputStream();
269 payload.writeTo(outputStream);
270 outputStream.flush();
271 }
272
273 final int status = connection.getResponseCode();
274 if (status >= HttpURLConnection.HTTP_BAD_REQUEST) {
275 final String error = InputOutput.pumpToString(connection.getErrorStream(),
276 StandardCharsets.UTF_8);
277 final String msg = "Error connecting to " + url + '(' + status + "): " + error;
278 throw new IOException(msg);
279 }
280 connection.disconnect();
281 }
282
283
288 private URLConnection openConnection() throws IOException {
289 final URLConnection connection = url.openConnection();
290 connection.setUseCaches(false);
291 if (CONNECTION_TIMEOUT > 0) {
292 connection.setConnectTimeout(CONNECTION_TIMEOUT);
293 }
294 if (READ_TIMEOUT > 0) {
295 connection.setReadTimeout(READ_TIMEOUT);
296 }
297
298
299 connection.setRequestProperty("Accept-Encoding", "gzip");
300 if (headers != null) {
301 for (final Map.Entry<String, String> entry : headers.entrySet()) {
302 connection.setRequestProperty(entry.getKey(), entry.getValue());
303 }
304 }
305 if (url.getUserInfo() != null) {
306 final String authorization = Base64Coder.encodeString(url.getUserInfo());
307 connection.setRequestProperty("Authorization", "Basic " + authorization);
308 }
309 return connection;
310 }
311
312
320 private static Serializable read(URLConnection connection, InputStream inputStream)
321 throws IOException, ClassNotFoundException {
322 InputStream input = inputStream;
323 try {
324 if ("gzip".equals(connection.getContentEncoding())) {
325
326
327 input = new GZIPInputStream(input);
328 }
329 final String contentType = connection.getContentType();
330 final TransportFormat transportFormat;
331 if (contentType != null) {
332 if (contentType.startsWith("text/xml")) {
333 transportFormat = TransportFormat.XML;
334 } else if (contentType.startsWith("text/html")) {
335 throw new IllegalStateException(
336 "Unexpected html content type, maybe not authentified");
337 } else {
338 transportFormat = TransportFormat.SERIALIZED;
339 }
340 } else {
341 transportFormat = TransportFormat.SERIALIZED;
342 }
343 return transportFormat.readSerializableFrom(input);
344 } finally {
345 try {
346 input.close();
347 } finally {
348 close(connection);
349 }
350 }
351 }
352
353 private static void close(URLConnection connection) throws IOException {
354
355
356 connection.getInputStream().close();
357
358 if (connection instanceof HttpURLConnection) {
359 final InputStream error = ((HttpURLConnection) connection).getErrorStream();
360 if (error != null) {
361 error.close();
362 }
363 }
364 }
365
366 private static boolean shouldMock() {
367 return Boolean.parseBoolean(
368 System.getProperty(Parameters.PARAMETER_SYSTEM_PREFIX + "mockLabradorRetriever"));
369 }
370
371
372 @SuppressWarnings("unchecked")
373 private <T> T createMockResultOfCall() throws IOException {
374 final Object result;
375 final String request = url.toString();
376 if (!request.contains(HttpParameter.PART.getName() + '=')
377 && !request.contains(HttpParameter.JMX_VALUE.getName())
378 || request.contains(HttpPart.DEFAULT_WITH_CURRENT_REQUESTS.getName())) {
379 final String message = request.contains("/test2") ? null
380 : "ceci est message pour le rapport";
381 result = Arrays.asList(new Counter(Counter.HTTP_COUNTER_NAME, null),
382 new Counter("services", null), new Counter(Counter.ERROR_COUNTER_NAME, null),
383 new JavaInformations(null, true), message);
384 } else {
385 result = LabradorMock.createMockResultOfPartCall(request);
386 }
387 return (T) result;
388 }
389
390 private static class LabradorMock {
391
392 static Object createMockResultOfPartCall(String request) throws IOException {
393
394 final Object result;
395 if (request.contains(HttpPart.SESSIONS.getName())
396 && request.contains(HttpParameter.SESSION_ID.getName())) {
397 result = null;
398 } else if (request.contains(HttpPart.SESSIONS.getName())
399 || request.contains(HttpPart.PROCESSES.getName())
400 || request.contains(HttpPart.JNDI.getName())
401 || request.contains(HttpPart.CONNECTIONS.getName())
402 || request.contains(HttpPart.MBEANS.getName())
403 || request.contains(HttpPart.HOTSPOTS.getName())) {
404 result = Collections.emptyList();
405 } else if (request.contains(HttpPart.CURRENT_REQUESTS.getName())
406 || request.contains(HttpPart.WEBAPP_VERSIONS.getName())
407 || request.contains(HttpPart.DEPENDENCIES.getName())) {
408 result = Collections.emptyMap();
409 } else if (request.contains(HttpPart.DATABASE.getName())) {
410 try {
411 result = new DatabaseInformations(0);
412 } catch (final Exception e) {
413 throw new IllegalStateException(e);
414 }
415 } else if (request.contains(HttpPart.HEAP_HISTO.getName())) {
416 try (InputStream input = LabradorMock.class.getResourceAsStream("/heaphisto.txt")) {
417 result = new HeapHistogram(input, false);
418 }
419 } else if (request.contains(HttpPart.LAST_VALUE.getName())) {
420 result = -1d;
421 } else if (request.contains(HttpParameter.JMX_VALUE.getName())) {
422 result = "-1";
423 } else if (request.contains(HttpPart.JVM.getName())) {
424 result = Collections
425 .singletonList(new JavaInformations(Parameters.getServletContext(), false));
426 } else {
427 result = null;
428 }
429 return result;
430 }
431
432 }
433 }
434