root/trunk/test/java/org/jcoderz/commons/connector/http/transport/ClientHandler.java

Revision 1011, 22.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.commons.connector.http.transport;
34
35import java.io.ByteArrayOutputStream;
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.io.UnsupportedEncodingException;
40import java.net.Socket;
41import java.util.HashMap;
42import java.util.Iterator;
43import java.util.Map;
44import java.util.logging.Logger;
45import org.jcoderz.commons.util.Constants;
46
47
48
49/**
50 * Handler for http client requests.
51 */
52public class ClientHandler
53      extends Thread
54{
55   /** Class name used for logging. */
56   private static final String CLASSNAME
57         = ClientHandler.class.getName();
58   /** Logger in use. */
59   private static final Logger logger
60         = Logger.getLogger(CLASSNAME);
61   private static final int MAX_REQUESTS = 1000;
62   private static final int SLEEP_TIME_BETWEEN_REQUESTS = 50;
63   private static final int BUFFER_SIZE = 4096;
64   private static final String NEW_LINE = "\n";
65   private static final String UTF8 = "UTF-8";
66   private final Socket mIncoming;
67   private final int mCounter;
68   private InputStream mInStream = null;
69   private OutputStream mOutStream = null;
70   private boolean mHaveToStop = false;
71
72   // request
73   private String mRequestLine;
74   private final Map mRequestParameter = new HashMap();
75   private String mRequestBody;
76   private String mRequestContentType;
77   private boolean mConnectionHaveToBeClosed = false;
78   private boolean mDoNotRespond = false;
79   private boolean mDoImmediateClose = false;
80   private boolean mUseEmptyResponse = false;
81
82   // response
83   private String mResponseLine;
84   private String mResponseBody;
85   private final Map mResponseParameter = new HashMap();
86
87   /**
88    * Constructor.
89    *
90    * @param incoming
91    *          the incoming socket
92    * @param counter
93    *          the number of handler
94    */
95   public ClientHandler (Socket incoming, int counter)
96   {
97      mIncoming = incoming;
98      mCounter = counter;
99
100      logger.info("+++ Creating Thread "
101            + mCounter + "x"
102            + " for Socket(" + incoming.getPort() + ")\n");
103   }
104
105   /**
106    * Starting handler thread.
107    */
108   public void run ()
109   {
110      try
111      {
112         mInStream = mIncoming.getInputStream();
113         mOutStream = mIncoming.getOutputStream();
114         for (int i = 1; i < MAX_REQUESTS; i++)
115         {
116            if (!gotIncomingRequest())
117            {
118               break;
119            }
120
121            logger.info(
122                  "+++ ITERATION " + i + "x Thread " + mCounter + "\n");
123            try
124            {
125               // read response
126               readRequest();
127
128               // if the client wants an immediate close without response
129               if (mDoImmediateClose)
130               {
131                  break;
132               }
133
134               // if the client do not want to receive a response
135               if (!mDoNotRespond)
136               {
137                  writeResponse();
138               }
139            }
140            catch (Exception ie)
141            {
142               logger.warning(
143                     "Exception (" + ie.getMessage() + ") while read/write");
144               ie.printStackTrace();
145               break;
146            }
147
148            if (mConnectionHaveToBeClosed)
149            {
150               break;
151            }
152         } // end for
153         mInStream.close();
154         mOutStream.close();
155         mIncoming.close();
156      }
157      catch (Exception ex)
158      {
159         ex.printStackTrace();
160      }
161      logger.info("+++ Terminating Thread " + mCounter + "x\n");
162   }
163
164   private boolean gotIncomingRequest ()
165   {
166      int bytesAvailable = 0;
167      boolean handleRequest = true;
168      for (;;)
169      {
170         try
171         {
172            bytesAvailable = mInStream.available();
173         }
174         catch (IOException ioe)
175         {
176            logger.warning(
177                  "IOException (" + ioe.getMessage()
178                  + ") on inputstream");
179            handleRequest = false;
180            break;
181         }
182
183         // take a break..
184         try
185         {
186            Thread.sleep(SLEEP_TIME_BETWEEN_REQUESTS);
187         }
188         catch (InterruptedException ie)
189         {
190            // ignore
191         }
192
193         if (bytesAvailable > 0)
194         {
195            break;
196         }
197         if (mHaveToStop)
198         {
199            //hangUp = true;
200            handleRequest = false;
201            break;
202         }
203
204      } // end for
205      return handleRequest;
206   }
207
208
209   /**
210    * Reads the request.
211    * @throws Exception
212    *          in case of an error
213    */
214   public void readRequest ()
215         throws Exception
216   {
217      // read the response message
218      try
219      {
220         // try to read <POST / HTTP/1.0>
221         readRequestLine();
222
223         // try to read
224         //             <Connection: Keep-Alive> etc.
225         readHeaderAttributes();
226
227         // try to read
228         //              request body
229         readRequestBody();
230      }
231      catch (Exception ex)
232      {
233         throw ex;
234      }
235
236      // dump request
237      final StringBuffer buffer = new StringBuffer();
238      buffer.append(
239            "\n- REQUEST ------------------------------------------\n");
240      buffer.append(requestToString());
241      buffer.append(
242            "\n-----------------------------------------------------\n");
243      logger.info(buffer.toString());
244   }
245
246
247   /**
248    * Reads the request header line.
249    * @throws Exception
250    *          in case of an error
251    */
252   protected void readRequestLine ()
253         throws Exception
254   {
255      String errorText = null; // used by throwing exceptions
256
257      String path = null;
258      String httpVersion = null;
259
260      try
261      {  // try to find a HTTP status line in the read input data
262         mRequestLine = readLine();
263         while (mRequestLine != null && !mRequestLine.startsWith("POST"))
264         {
265            mRequestLine = readLine();
266         }
267      }
268      catch (IOException ioe)
269      {
270         errorText = "IOException with message: " + ioe.getMessage();
271         throw new Exception(errorText);
272      }
273
274      if (mRequestLine == null)
275      {
276         // A null requestLine means the connection was lost
277         // before we got a response.  Try again.
278         errorText = "Error in parsing the request line : unable to find line"
279               + " starting with \"POST\"";
280         throw new Exception(errorText);
281      }
282      // <POST> found...
283
284      final int pathIndex = mRequestLine.indexOf("/");
285      int afterPathIndex = 0;
286      if (pathIndex > 0)
287      {
288         afterPathIndex = mRequestLine.indexOf(" ", pathIndex);
289         if (afterPathIndex > pathIndex)
290         {
291            path = mRequestLine.substring(pathIndex, afterPathIndex - 1);
292         }
293      }
294      if (path == null)
295      {
296         errorText = "Error in parsing the request line : unable to find path";
297         throw new Exception(errorText);
298      }
299      // <POST /> found..
300
301      httpVersion = mRequestLine.substring(afterPathIndex).trim();
302      if (httpVersion == null)
303      {
304         errorText
305            = "Error in parsing the request line : unable to find HTTP version";
306         throw new Exception(errorText);
307      }
308      // <POST / HTTP/1.x> found..
309   }
310
311   /**
312    * Reads the header attributes.
313    * @throws Exception
314    *          in case of an error
315    */
316   private void readHeaderAttributes ()
317         throws Exception
318   {
319      String errorText = null; // used by throwing exceptions
320      String headerLine = null;
321      for (;;)
322      {
323         try
324         {
325            headerLine = readLine();
326         }
327         catch (IOException ioe)
328         {
329            errorText = "IOException with message: " + ioe.getMessage();
330            throw new Exception(errorText);
331         }
332
333         // if all header attributes read..
334         if ((headerLine == null) || (headerLine.length() < 1))
335         {
336            break;
337         }
338
339         final int colon = parseHeaderLine(headerLine);
340         final String name = headerLine.substring(0, colon).trim();
341         final String value = headerLine.substring(colon + 1).trim();
342
343         addRequestHeaderParameter(name, value);
344      } // end for
345
346      assertRequestParameterGiven();
347
348      // remember the Content-Type header
349      mRequestContentType
350            = (String) mRequestParameter.get("content-type");
351
352      checkSpecialParameter();
353      setEchoParameterToResponse();
354   }
355
356   private void checkSpecialParameter ()
357   {
358      // check for Connection: Close
359      final String connectionValue
360            = (String) mRequestParameter.get("connection");
361
362      if (connectionValue != null
363            && connectionValue.toLowerCase(
364                  Constants.SYSTEM_LOCALE).equals("close"))
365      {
366         mConnectionHaveToBeClosed = true;
367      }
368
369      // check for parameter "DoNotRespond"
370      final String doNotRespondValue
371            = (String) mRequestParameter.get("donotrespond");
372
373      if (doNotRespondValue != null
374            && doNotRespondValue.toLowerCase(
375                  Constants.SYSTEM_LOCALE).equals("true"))
376      {
377         mDoNotRespond = true;
378      }
379
380      // check for parameter "DoImmediateClose"
381      final String doImmediateCloseValue
382            = (String) mRequestParameter.get("doimmediateclose");
383
384      if (doImmediateCloseValue != null
385            && doImmediateCloseValue.toLowerCase(
386                  Constants.SYSTEM_LOCALE).equals("true"))
387      {
388         mDoImmediateClose = true;
389      }
390
391      // check for parameter "UseEmptyResponse"
392      final String useEmptyResponse
393            = (String) mRequestParameter.get("useemptyresponse");
394
395      if (useEmptyResponse != null
396            && useEmptyResponse.toLowerCase(
397                  Constants.SYSTEM_LOCALE).equals("true"))
398      {
399         mUseEmptyResponse = true;
400      }
401   }
402
403   /**
404    * Detects a request parameter with prefix "ECHO_" in request
405    * and adds these parameter without prefix to response.
406    */
407   private void setEchoParameterToResponse ()
408   {
409      for (final Iterator it = mRequestParameter.keySet().iterator();
410            it.hasNext();)
411      {
412         final String key = (String) it.next();
413         final String value = (String) mRequestParameter.get(key);
414
415         final String prefix = "echo_";
416         final int prefixLength = prefix.length();
417         if (key.startsWith(prefix))
418         {
419            final String responseKey = key.substring(prefixLength);
420            mResponseParameter.put(responseKey, value);
421         }
422      }
423   }
424
425   private int parseHeaderLine (String headerLine)
426         throws Exception
427   {
428      // Parse the header name and value
429      final int colon = headerLine.indexOf(":");
430      if (colon < 0)
431      {
432         final String errorText
433            = "Unable to parse header - no colon found: " + headerLine;
434         throw new Exception(errorText);
435      }
436      return colon;
437   }
438
439   private void addRequestHeaderParameter (String name, String value)
440   {
441      if (name != null && value != null)
442      {
443         mRequestParameter.put(name.toLowerCase(
444               Constants.SYSTEM_LOCALE), value);
445         logger.info("Header Parameter: " + name + "=" + value);
446      }
447   }
448
449   private void assertRequestParameterGiven ()
450         throws Exception
451   {
452      if (mRequestParameter.isEmpty())
453      {
454         final String errorText = "no header parameter found";
455         throw new Exception(errorText);
456      }
457   }
458
459   /**
460    * Reads the request body.
461    * @throws Exception
462    *          in case of an error
463    */
464   private void readRequestBody ()
465         throws Exception
466   {
467      String errorText = null; // used by throwing exceptions
468      byte[] responseBody = null;
469
470      // check Content-Length
471      final String stringValue
472            = (String) mRequestParameter.get("content-length");
473      if (stringValue == null)
474      {
475         errorText = "no Content-Length attribute found";
476         logger.warning("Throwing Exception: " + errorText);
477         throw new Exception(errorText);
478      }
479      final int expectedLength = Integer.parseInt(stringValue);
480
481      // check Content-Type
482      final String encoding = getEncoding();
483      if (encoding == null)
484      {
485         errorText = "no Content-Type attribute found";
486         logger.warning("Throwing Exception: " + errorText);
487         throw new Exception(errorText);
488      }
489
490      try
491      {
492         responseBody = read(expectedLength);
493      }
494      catch (IOException ioe)
495      {
496         errorText = "IOException with message: " + ioe.getMessage();
497         throw new Exception(errorText);
498      }
499
500      try
501      {
502         mRequestBody
503            = new String(responseBody, 0, responseBody.length, encoding);
504      }
505      catch (UnsupportedEncodingException ee)
506      {
507         errorText = "error while reading body: "
508                  + " encoding (" + encoding + ") is not supported";
509         logger.warning("Throwing Exception: " + errorText);
510         throw new Exception(errorText);
511      }
512   }
513
514   /**
515    * Reads a line from InputStream.
516    * @return String
517    *          the read line
518    * @throws IOException
519    *          in case of an error
520    */
521   public String readLine ()
522         throws IOException
523   {
524      String result = null;
525      StringBuffer buffer = new StringBuffer();
526      for (;;)
527      {
528         final int ch = mInStream.read();
529         if (ch < 0)
530         {
531            if (buffer.length() == 0)
532            {
533               buffer = null;
534               break;
535            }
536            else
537            {
538               break;
539            }
540         }
541         else if (ch == '\r')
542         {
543            continue;
544         }
545         else if (ch == '\n')
546         {
547            break;
548         }
549         buffer.append((char) ch);
550      }
551      if (buffer != null)
552      {
553         result = buffer.toString();
554      }
555      return result;
556   }
557
558   /**
559    * Reads the response body.
560    *
561    * @param expectedLength
562    *          the expected length of body
563    * @return byte[]
564    *          the request body
565    * @throws IOException
566    *          in case of an error
567    */
568   public byte[] read (int expectedLength)
569         throws IOException
570   {
571      final byte[] buffer = new byte[BUFFER_SIZE]; // todo ..configurable!
572      final ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
573
574      int bytesRead = 0;
575      int foundLength = 0;
576
577      // if expectedLength == -1 ..infinite read til timeout
578      //    at the moment a header with "Content-Length" -1 is invalid
579      //    and this infinite read scenario is not possible..
580      while (expectedLength == -1 || foundLength < expectedLength)
581      {
582         bytesRead = mInStream.read(buffer);
583
584         // no bytes read
585         if (bytesRead == -1)
586         {
587            break;
588         }
589         tempOut.write(buffer, 0, bytesRead);
590         foundLength += bytesRead;
591         if (expectedLength > -1)
592         {
593            // everything read as expected
594            if (foundLength == expectedLength)
595            {
596               break;
597            }
598            else if (foundLength > expectedLength)
599            {
600               final StringBuffer strbuf = new StringBuffer();
601               strbuf.append("++ WARNING WHILE READING RESPONSE BODY ++\n");
602               strbuf.append("++ expected length (" + expectedLength
603                     + ") exceeded ++\n");
604               strbuf.append("++ found " + foundLength + " bytes ++");
605               logger.warning(strbuf.toString());
606               break;
607            }
608         }
609      } // end while
610
611      try
612      {
613         tempOut.close();
614      }
615      catch (IOException ioe)
616      {
617         final String errorText
618               = "unable to close buffer with data read from socket"
619                  + " as response body";
620         throw new IOException(errorText);
621      }
622      return tempOut.toByteArray();
623   }
624
625   /**
626    * Gets the encoding.
627    * @return String
628    *          the used encoding
629    */
630   public String getEncoding ()
631   {
632      String result = null;
633      final String contentType = (String) mRequestParameter.get("content-type");
634
635      if (contentType != null)
636      {
637         if (contentType.toUpperCase(
638               Constants.SYSTEM_LOCALE).equals("TEXT/PLAIN"))
639         {
640            result = UTF8;
641         }
642         else
643         {
644            result = getEncodingFromCharsetValue (contentType);
645         }
646      }
647      return result;
648   }
649
650   /**
651    * Gets the encoding extracted from parameter value CHARSET.
652    *
653    * @param contentType
654    *          the parameter value set for Content-Type
655    * @return String
656    *          the encoding extracted from CHARSET if found in
657    *          Content-Type, null else
658    */
659   private String getEncodingFromCharsetValue (String contentType)
660   {
661      String result = UTF8; // as default
662      final String charsetName = "CHARSET="; // must be upper case
663      final int charsetIndex
664            = contentType.toUpperCase(Constants.SYSTEM_LOCALE).
665                  indexOf(charsetName);
666      if (charsetIndex > 0)
667      {
668         final int quote1 = contentType.
669            indexOf("\"", charsetIndex + charsetName.length());
670         final int quote2 = contentType.
671            indexOf("\"", quote1 + 1);
672         if (quote1 > 0)
673         {
674            result = contentType.
675               substring(quote1 + 1, quote2).
676                     toUpperCase(Constants.SYSTEM_LOCALE);
677         }
678         else
679         {
680            result = contentType.
681               substring(charsetIndex + charsetName.length()).
682                     toUpperCase(Constants.SYSTEM_LOCALE);
683         }
684      }
685      else
686      {
687         final String messageText = "no charset defined in Content-Type ("
688                                 + contentType + ")";
689         logger.info(messageText);
690      }
691      return result;
692   }
693
694   /**
695    * Writes the response message.
696    * @throws Exception
697    *          in case of an error
698    */
699   public void writeResponse ()
700         throws Exception
701   {
702      StringBuffer buffer = new StringBuffer();
703
704      // create response
705      // header line
706      mResponseLine = "HTTP/1.0 200 OK";
707
708      // body
709      if (!mUseEmptyResponse)
710      {
711         buffer.append("Echo:");
712         buffer.append(mRequestBody);
713      }
714      mResponseBody = buffer.toString();
715
716      // the message to be send
717      final byte[] messageAsByte = mResponseBody.getBytes(UTF8);
718      //
719
720      // header parameter
721      if (mConnectionHaveToBeClosed)
722      {
723         mResponseParameter.put("Connection", "Close");
724      }
725      else
726      {
727         mResponseParameter.put("Connection", "Keep-Alive");
728      }
729
730      final int bodyLength =  messageAsByte.length;
731      mResponseParameter.put(
732            "Content-Length", Integer.toString(bodyLength));
733      mResponseParameter.put("Content-Type", mRequestContentType);
734
735      // complete response
736      buffer = new StringBuffer();
737      buffer.append(mResponseLine);
738      buffer.append(NEW_LINE);
739      buffer.append(getResponseParameterAsString());
740      buffer.append(NEW_LINE);
741      buffer.append(mResponseBody);
742
743      // ..for dumping
744      final StringBuffer strbuf = new StringBuffer();
745      strbuf.append("\n-- RESPONSE --\n");
746      strbuf.append(buffer.toString());
747      strbuf.append("\n--------------\n");
748      logger.info(strbuf.toString());
749
750      // complete message as byte array
751      final byte[] messageAsBytes = buffer.toString().getBytes(UTF8);
752
753      mOutStream.write(messageAsBytes);
754   }
755
756   /**
757    * Gets response header parameter.
758    * @return  String
759    *          parameter ready for sending
760    */
761   private String getResponseParameterAsString ()
762   {
763      final StringBuffer buffer = new StringBuffer();
764      final Iterator keyIterator = mResponseParameter.keySet().iterator();
765      while (keyIterator.hasNext())
766      {
767         final String key = (String) keyIterator.next();
768         final String value = (String) mResponseParameter.get(key);
769         buffer.append(key);
770         buffer.append(": ");
771         buffer.append(value);
772         buffer.append(NEW_LINE);
773      }
774      return buffer.toString();
775   }
776
777   /**
778    * Gets the request as string.
779    * @return String
780    *          the incoming request
781    */
782   private String requestToString ()
783   {
784      final StringBuffer buffer = new StringBuffer();
785      buffer.append(mRequestLine);
786      buffer.append(NEW_LINE);
787      final Iterator keyIterator = mRequestParameter.keySet().iterator();
788      while (keyIterator.hasNext())
789      {
790         final String key = (String) keyIterator.next();
791         final String value = (String) mRequestParameter.get(key);
792         buffer.append(key);
793         buffer.append(": ");
794         buffer.append(value);
795         buffer.append(NEW_LINE);
796      }
797      buffer.append(NEW_LINE);
798      buffer.append(mRequestBody);
799      return buffer.toString();
800   }
801
802   /**
803    * Checks flag indicating to stop handler thread.
804    */
805   public void haveToStop ()
806   {
807      mHaveToStop = true;
808   }
809
810}
Note: See TracBrowser for help on using the browser.