root/trunk/src/java/org/jcoderz/phoenix/servlet/PingServlet.java

Revision 1011, 14.2 kB (checked in by amandel, 4 years ago)

Aligned svn keyword settings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.phoenix.servlet;
34
35import java.awt.Color;
36import java.io.DataInputStream;
37import java.io.IOException;
38import java.io.PrintWriter;
39import java.io.StringWriter;
40import java.net.InetAddress;
41import java.net.Socket;
42import java.net.UnknownHostException;
43import java.text.DateFormat;
44import java.text.SimpleDateFormat;
45import java.util.Collections;
46import java.util.ConcurrentModificationException;
47import java.util.Date;
48import java.util.HashMap;
49import java.util.Iterator;
50import java.util.Locale;
51import java.util.Map;
52import java.util.TimeZone;
53
54import javax.servlet.http.HttpServlet;
55import javax.servlet.http.HttpServletRequest;
56import javax.servlet.http.HttpServletResponse;
57
58import org.jcoderz.commons.util.IoUtil;
59
60/**
61 * This servlet sends a simple ping to the given host and returns a
62 * 1 pixel image with a color representing the host status.
63 *
64 * @web.servlet name="ping"
65 * @web.servlet-mapping url-pattern="/ping/*"
66 *
67 * @author Andreas Mandel
68 */
69public final class PingServlet
70      extends HttpServlet
71{
72   /**
73    * Timezone used by the protocol.
74    */
75   public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
76
77   /** The format used to write date values. */
78   public static final String DATE_TIME_FORMAT
79         = "yyyy-MM-dd HH:mm:ss.SSS";
80
81   private static final int HOST_NOT_REACHED = -1;
82   private static final int UNKNOWN_HOST = -2;
83
84   private static final int ILLEGAL_ARGUMENT = -3;
85   private static final int HOST_REFUSED = -4;
86
87   private static final long serialVersionUID = 1L;
88
89   private static final int MAX_PING_CACHE_SIZE = 1000;
90   private static final Map PING_CACHE
91         = Collections.synchronizedMap(new HashMap());
92   private static long sLastCacheUpdate = 0;
93   private static boolean sUpdateInProgreess = false;
94
95   private static final long VALIDITY_TIME = 10 * 60 * 1000;
96
97   /**
98    * If a value in the cache in not used for this period of time it is
99    * removed from the cache.
100    */
101   private static final long MAX_IDLE_TIME = 7 * 24 * 60 * 60 * 1000;
102
103   private static final int SOCKET_TIMEOUT = 1000;
104
105   private static final long FAST_RESPONSE = 5;
106
107   private static final int COLOR_OFFSET_RED =  13;
108   private static final int COLOR_OFFSET_GREEN = COLOR_OFFSET_RED + 1;
109   private static final int COLOR_OFFSET_BLUE = COLOR_OFFSET_GREEN + 1;
110   private static final int BG_OFFSET_RED = COLOR_OFFSET_BLUE + 1;
111   private static final int BG_OFFSET_GREEN = BG_OFFSET_RED + 1;
112   private static final int BG_OFFSET_BLUE = BG_OFFSET_GREEN + 1;
113
114   private static final byte [] IMAGE_DATA
115         = {
116            'G', 'I', 'F', '8', '7', 'a', //
117            1, 0, // logical width
118            1, 0, // logical height
119            (byte) 0x80, //
120            2, // BG color index
121            0,  //
122            0, 0, 0, // COLOR 1 (used)
123            0, 0, 0, // COLOR 2 (unused)
124            44,      // IMAGE
125            0, 0,    // Left POS
126            0, 0,    // Right POS
127            1, 0,    // WIDTH
128            1, 0,    // HEIGHT
129            0,
130            2, 2, 68, 01, 0, 59
131      };
132
133   private static final int ECHO_PORT = 22;
134
135   protected void doPost (HttpServletRequest req, HttpServletResponse rsp)
136         throws IOException
137   {
138      doGet(req, rsp);
139   }
140
141   protected void doGet (HttpServletRequest req, HttpServletResponse rsp)
142         throws IOException
143   {
144      String hostname = req.getParameter("host");
145
146      final String clear = req.getParameter("clear");
147      if (clear != null && clear.length() > 0)
148      {
149         PING_CACHE.clear();
150      }
151
152      if (hostname == null || hostname.length() == 0)
153      {
154         hostname = req.getPathInfo();
155         if (hostname != null && hostname.charAt(0) == '/')
156         {
157            hostname = hostname.substring(1);
158         }
159      }
160
161      if (hostname == null || hostname.length() == 0)
162      {
163         dumpCacheInfo(rsp);
164      }
165      else
166      {
167         sendAnswer(check(hostname), rsp);
168      }
169
170      updateCache();
171   }
172
173   private void dumpCacheInfo (HttpServletResponse rsp)
174         throws IOException
175   {
176      rsp.setContentType("text/plain");
177
178      final StringWriter sw = new StringWriter();
179      final PrintWriter data = new PrintWriter(sw);
180      synchronized (PING_CACHE)
181      {
182         data.println("Last Update: " + dateToString(sLastCacheUpdate));
183         data.println("Update in progress: " + sUpdateInProgreess);
184         data.println("Number of stored results: " + PING_CACHE.size());
185         data.println("Cached Data:");
186         data.println("---");
187      }
188      final PrintWriter out = rsp.getWriter();
189      out.print(sw.getBuffer().toString());
190
191      try
192      {
193         final Iterator i = PING_CACHE.values().iterator();
194
195         while (i.hasNext())
196         {
197            final PingResult result = (PingResult) i.next();
198            out.println(result);
199         }
200         out.println("---");
201      }
202      catch (ConcurrentModificationException ex)
203      {
204         out.println("Uups... somebody updated the cache... try again!");
205      }
206   }
207
208   private void updateCache ()
209   {
210      try
211      {
212         final long now = System.currentTimeMillis();
213         final long lastUpdate;
214         final boolean alreadyUpdating;
215         synchronized (PING_CACHE)
216         {
217            lastUpdate = sLastCacheUpdate;
218            alreadyUpdating = sUpdateInProgreess;
219            sUpdateInProgreess = true;
220         }
221
222         if (lastUpdate + VALIDITY_TIME < now && !alreadyUpdating)
223         {
224            final Iterator i = PING_CACHE.values().iterator();
225
226            while (i.hasNext())
227            {
228               final PingResult result = (PingResult) i.next();
229               if (result.getLastUsed() < (now - MAX_IDLE_TIME))
230               {
231                  i.remove();
232               }
233               else if (result.getExpires() < now)
234               {
235                  new Thread()
236                  {
237                     public void run ()
238                     {
239                        check(result.getHostname()); // update entry not list?
240                     };
241                  } .start();
242               }
243            }
244
245            synchronized (PING_CACHE)
246            {
247               sLastCacheUpdate = now;
248            }
249         }
250      }
251      catch (ConcurrentModificationException ex)
252      {
253         // we will catch up next time
254      }
255      finally
256      {
257         synchronized (PING_CACHE)
258         {
259            sUpdateInProgreess = false;
260         }
261      }
262   }
263
264
265   private void sendAnswer (PingResult result, HttpServletResponse rsp)
266         throws IOException
267   {
268      rsp.setContentType("image/gif");
269      rsp.setContentLength(IMAGE_DATA.length);
270      rsp.setHeader("Cache-Control", "public");
271
272      final byte[] responseData = new byte[IMAGE_DATA.length];
273      System.arraycopy(IMAGE_DATA, 0, responseData, 0, IMAGE_DATA.length);
274
275      if (result != null)
276      {
277         rsp.setDateHeader("Last-Modified", result.getLastModified());
278         rsp.setDateHeader("Expires", result.getExpires());
279         final Color color = resultToColor(result.getResult());
280
281         responseData[COLOR_OFFSET_RED] = (byte) color.getRed();
282         responseData[COLOR_OFFSET_GREEN] = (byte) color.getGreen();
283         responseData[COLOR_OFFSET_BLUE] = (byte) color.getBlue();
284         responseData[BG_OFFSET_RED] = (byte) color.getRed();
285         responseData[BG_OFFSET_GREEN] = (byte) color.getGreen();
286         responseData[BG_OFFSET_BLUE] = (byte) color.getBlue();
287         rsp.setHeader("response-value", String.valueOf(result.getResult()));
288         rsp.setHeader("response-host", result.getHostname());
289      }
290
291      rsp.getOutputStream().write(responseData);
292   }
293
294
295   private Color resultToColor (long result)
296   {
297      final Color responseColor;
298      if (result == UNKNOWN_HOST)
299      {
300         responseColor = Color.RED;
301      }
302      else if (result == ILLEGAL_ARGUMENT)
303      {
304         responseColor = Color.ORANGE;
305      }
306      else if (result == HOST_REFUSED)
307      {
308         responseColor = Color.YELLOW;
309      }
310      else if (result < 0)
311      {
312         responseColor = Color.BLACK;
313      }
314      else if (result <= FAST_RESPONSE)
315      {
316         responseColor = Color.GREEN.brighter();
317      }
318      else if (result <= (FAST_RESPONSE + FAST_RESPONSE))
319      {
320         responseColor = Color.GREEN;
321      }
322      else if (result <= (FAST_RESPONSE + FAST_RESPONSE + FAST_RESPONSE))
323      {
324         responseColor = Color.GREEN.darker();
325      }
326      else
327      {
328         responseColor = Color.GREEN.darker().darker();
329      }
330      return responseColor;
331   }
332
333   protected PingResult check (String host)
334   {
335      final long now = System.currentTimeMillis();
336
337      PingResult result = (PingResult) PING_CACHE.get(host);
338      if (result == null)
339      {
340          result = new PingResult(host, ping(host));
341          PING_CACHE.put(host, result);
342      }
343      else
344      {
345            // only one update per host
346            synchronized (result)
347            {
348               if (result.getExpires() < now)
349               {  // this might take some time!
350                  result.setResult(ping(host));
351               }
352            }
353      }
354
355      // just to be save against memory attacks
356      if (PING_CACHE.size() > MAX_PING_CACHE_SIZE)
357      {
358         PING_CACHE.clear();
359      }
360
361      return result;
362   }
363
364
365   protected static long ping (String hostname)
366   {
367      final long result;
368      if (hostname == null || hostname.length() == 0)
369      {
370         result = ILLEGAL_ARGUMENT;
371      }
372      else
373      {
374         InetAddress host = null;
375         try
376         {
377            host = InetAddress.getByName(hostname);
378         }
379         catch (UnknownHostException ex)
380         {
381            // hmmm
382         }
383
384         if (host == null)
385         {
386            result = UNKNOWN_HOST;
387         }
388         else
389         {
390            result = ping(host);
391         }
392      }
393      return result;
394   }
395
396
397   protected static long ping (InetAddress host)
398   {
399      final long timer = System.currentTimeMillis();
400      DataInputStream dis = null;
401      Socket t = null;
402      long result = -1;
403      try
404      {
405         t = new Socket(host, ECHO_PORT);
406         t.setTcpNoDelay(true);
407         t.setSoTimeout(SOCKET_TIMEOUT);
408         dis = new DataInputStream(t.getInputStream());
409         dis.readByte();
410         result = System.currentTimeMillis() - timer;
411      }
412      catch (IOException e)
413      {
414         final String message = e.getMessage();
415         if (message != null && message.indexOf("refused") != -1)
416         {
417            result = HOST_REFUSED;
418         }
419         else
420         {
421            result = HOST_NOT_REACHED;
422         }
423      }
424      finally
425      {
426         IoUtil.close(dis);
427         IoUtil.close(t);
428      }
429      return result;
430   }
431
432   static String dateToString (long time)
433   {
434      final DateFormat formater
435            = new SimpleDateFormat(DATE_TIME_FORMAT, Locale.US);
436      formater.setTimeZone(TIME_ZONE);
437      return formater.format(new Date(time));
438   }
439
440   private static class PingResult
441   {
442      private final String mHostname;
443      private long mResult;
444      private long mExpires;
445      private long mLastModified;
446      private long mLastUsed;
447
448      PingResult (String hostname, long result)
449      {
450         final long now = System.currentTimeMillis();
451         mExpires = now + VALIDITY_TIME;
452         mLastModified = now;
453         mHostname = hostname;
454         mResult = result;
455         mLastUsed = now;
456      }
457
458      /** {@inheritDoc} */
459      public synchronized String toString ()
460      {
461         final StringBuffer buffer = new StringBuffer();
462         buffer.append("[PingResult: ");
463         buffer.append(mHostname);
464         buffer.append(" result: ");
465         buffer.append(mResult);
466         buffer.append(" expires: ");
467         buffer.append(dateToString(mExpires));
468         buffer.append(" lastModified: ");
469         buffer.append(dateToString(mLastModified));
470         buffer.append(" lastUsed: ");
471         buffer.append(dateToString(mLastUsed));
472         buffer.append(']');
473         return buffer.toString();
474      }
475
476      String getHostname ()
477      {
478         return mHostname;
479      }
480
481      synchronized long getExpires ()
482      {
483         return mExpires;
484      }
485
486      synchronized long getLastModified ()
487      {
488         return mLastModified;
489      }
490
491      synchronized void setResult (long result)
492      {
493         final long now = System.currentTimeMillis();
494         mExpires = now + VALIDITY_TIME;
495         if (result != mResult)
496         {
497            mLastModified = now;
498            mResult = result;
499         }
500      }
501
502      synchronized long getResult ()
503      {
504         mLastUsed = System.currentTimeMillis();
505         return mResult;
506      }
507
508      synchronized long getLastUsed ()
509      {
510         return mLastUsed;
511      }
512   }
513}
Note: See TracBrowser for help on using the browser.