KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > excalibur > instrument > manager > http > server > HTTPServer


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14  * implied.
15  *
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */

19
20 package org.apache.excalibur.instrument.manager.http.server;
21
22 import java.io.BufferedReader JavaDoc;
23 import java.io.ByteArrayOutputStream JavaDoc;
24 import java.io.File JavaDoc;
25 import java.io.FileNotFoundException JavaDoc;
26 import java.io.FileWriter JavaDoc;
27 import java.io.InputStream JavaDoc;
28 import java.io.InputStreamReader JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.io.OutputStream JavaDoc;
31 import java.io.PrintWriter JavaDoc;
32 import java.io.UnsupportedEncodingException JavaDoc;
33 import java.net.InetAddress JavaDoc;
34 import java.net.Socket JavaDoc;
35 import java.text.SimpleDateFormat JavaDoc;
36 import java.util.ArrayList JavaDoc;
37 import java.util.Date JavaDoc;
38 import java.util.HashMap JavaDoc;
39 import java.util.List JavaDoc;
40 import java.util.Locale JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.StringTokenizer JavaDoc;
43
44 import org.apache.excalibur.instrument.CounterInstrument;
45
46 /**
47  *
48  * @author <a HREF="mailto:dev@avalon.apache.org">Avalon Development Team</a>
49  * @version CVS $Revision: 1.6 $ $Date: 2004/03/10 13:59:34 $
50  * @since 4.1
51  */

52 public class HTTPServer
53     extends AbstractSocketServer
54 {
55     /** List of registered HTTPURLHandlers. */
56     private List JavaDoc m_handlers = new ArrayList JavaDoc();
57     
58     /** Optimized array of the handler list that lets us avoid synchronization */
59     private HTTPURLHandler[] m_handlerArray;
60     
61     /** Access log file name. Null if not configured. */
62     private String JavaDoc m_accessLogFile;
63     
64     /** The currently open log file. May be null. */
65     private File JavaDoc m_currentLogFile;
66     
67     /** The currently open log PrintWriter. */
68     private PrintWriter JavaDoc m_currentLogWriter;
69     
70     /** DateFormat used when generating log file names. Only use when synchronized. */
71     private SimpleDateFormat JavaDoc m_dayFormat = new SimpleDateFormat JavaDoc( "yyyy-MM-dd" );
72     
73     /** DateFormat used when generating log entries. Only use when synchronized.
74      * The US Locale is set because other locales do not correctly show the month. */

75     private SimpleDateFormat JavaDoc m_logTimeFormat =
76         new SimpleDateFormat JavaDoc( "dd/MMM/yyyy:HH:mm:ss Z", Locale.US );
77     
78     /** Number of requests. */
79     private CounterInstrument m_instrumentRequests;
80     
81     /** Number of response bytes sent to the client. Includes all bytes, not
82      * only the content. */

83     private CounterInstrument m_instrumentResponseBytes;
84     
85     /** Number of request bytes received from the client. */
86     private CounterInstrument m_instrumentRequestBytes;
87     
88     /*---------------------------------------------------------------
89      * Constructors
90      *-------------------------------------------------------------*/

91     /**
92      * Creates a new HTTPServer.
93      *
94      * @param port The port on which the server will listen.
95      * @param bindAddress The address on which the server will listen for
96      * connections.
97      */

98     public HTTPServer( int port, InetAddress JavaDoc bindAddress )
99     {
100         super( port, bindAddress );
101         
102         addInstrument( m_instrumentRequests = new CounterInstrument( "requests" ) );
103         addInstrument( m_instrumentResponseBytes = new CounterInstrument( "response-bytes" ) );
104         addInstrument( m_instrumentRequestBytes = new CounterInstrument( "request-bytes" ) );
105     }
106     
107     /*---------------------------------------------------------------
108      * AbstractSocketServer Methods
109      *-------------------------------------------------------------*/

110     
111     /**
112      * Stops the server.
113      *
114      * @throws Exception If there are any problems stopping the component.
115      */

116     public void stop()
117         throws Exception JavaDoc
118     {
119         super.stop();
120         
121         // Close the logger if it is open.
122
synchronized( this )
123         {
124             if ( m_currentLogWriter != null )
125             {
126                 m_currentLogWriter.close();
127                 m_currentLogWriter = null;
128                 m_currentLogFile = null;
129             }
130         }
131     }
132     
133     /**
134      * Handle a newly connected socket. The implementation need not
135      * worry about closing the socket.
136      *
137      * @param socket Newly connected Socket to be handled.
138      */

139     protected void handleSocket( Socket JavaDoc socket )
140     {
141         if ( getLogger().isDebugEnabled() )
142         {
143             getLogger().debug( "handleSocket( " + socket + " ) BEGIN : "
144                 + Thread.currentThread().getName() );
145         }
146         
147         String JavaDoc ip = socket.getInetAddress().getHostAddress();
148         
149         try
150         {
151             // As long as we have valid requests, keep the connection open.
152
while ( handleRequest( socket.getInputStream(), socket.getOutputStream(), ip )
153                 && !isStopping() )
154             {
155             }
156         }
157         catch ( java.io.InterruptedIOException JavaDoc e ) // java.net.SocketTimeoutException not in 1.3
158
{
159             // The connection simply timed out and closed.
160
}
161         catch ( java.net.SocketException JavaDoc e )
162         {
163             // The connection simply timed out and closed.
164
}
165         catch ( Throwable JavaDoc e )
166         {
167             getLogger().debug( "Encountered an error processing the request.", e );
168         }
169         
170         if ( getLogger().isDebugEnabled() )
171         {
172             getLogger().debug( "handleSocket( " + socket + " ) END : "
173                 + Thread.currentThread().getName() );
174         }
175     }
176     
177     /*---------------------------------------------------------------
178      * Methods
179      *-------------------------------------------------------------*/

180     
181     /**
182      * Access log file name. Null if not configured.
183      * If the log file name contains the string "yyyy_mm_dd" then that
184      * token will be replaced with the current day and the file will
185      * be rolled each day at midnight.
186      *
187      * @param accessLogFile Name of the log file or null if disabled.
188      */

189     public void setAccessLogFile( String JavaDoc accessLogFile )
190     {
191         m_accessLogFile = accessLogFile;
192     }
193     
194     /**
195      * Registers a new HTTP URL Handler with the server.
196      *
197      * @param handler The handler to register.
198      */

199     public void registerHandler( HTTPURLHandler handler )
200     {
201         synchronized( m_handlers )
202         {
203             m_handlers.add( handler );
204             m_handlerArray = null;
205         }
206     }
207     
208     private void logAccessEvent( String JavaDoc ip,
209                                  String JavaDoc method,
210                                  String JavaDoc url,
211                                  int errorCode,
212                                  int contentLength,
213                                  String JavaDoc referrer,
214                                  String JavaDoc userAgent )
215     {
216         if ( m_accessLogFile == null )
217         {
218             return;
219         }
220         
221         Date JavaDoc now = new Date JavaDoc();
222         
223         synchronized( this )
224         {
225             File JavaDoc file;
226             int datePos = m_accessLogFile.indexOf( "yyyy_mm_dd" );
227             if ( datePos >= 0 )
228             {
229                 StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
230                 if ( datePos > 0 )
231                 {
232                     sb.append( m_accessLogFile.substring( 0, datePos ) );
233                 }
234                 sb.append( m_dayFormat.format( now ) );
235                 if ( datePos + 10 < m_accessLogFile.length() )
236                 {
237                     sb.append( m_accessLogFile.substring( datePos + 10 ) );
238                 }
239                 
240                 file = new File JavaDoc( sb.toString() );
241             }
242             else
243             {
244                 file = new File JavaDoc( m_accessLogFile );
245             }
246             
247             if ( ( m_currentLogFile == null ) || ( !m_currentLogFile.equals( file ) ) )
248             {
249                 // Open a new log file.
250
if ( m_currentLogWriter != null )
251                 {
252                     m_currentLogWriter.close();
253                 }
254                 try
255                 {
256                     m_currentLogWriter = new PrintWriter JavaDoc( new FileWriter JavaDoc( file ) );
257                     m_currentLogFile = file;
258                 }
259                 catch ( IOException JavaDoc e )
260                 {
261                     getLogger().warn( "Unable to open: " + m_currentLogFile );
262                     m_currentLogWriter = null;
263                     m_currentLogFile = null;
264                     return;
265                 }
266             }
267             
268             // Now log the entry.
269
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
270             sb.append( ip );
271             sb.append( " - - [" );
272             sb.append( m_logTimeFormat.format( now ) );
273             sb.append( "] \"" );
274             sb.append( method );
275             sb.append( " " );
276             sb.append( url );
277             sb.append( "\" " );
278             sb.append( errorCode );
279             sb.append( " " );
280             sb.append( contentLength );
281             sb.append( " \"" );
282             sb.append( referrer );
283             sb.append( "\" \"" );
284             sb.append( userAgent );
285             sb.append( "\"" );
286             
287             m_currentLogWriter.println( sb.toString() );
288             m_currentLogWriter.flush();
289         }
290     }
291     
292     private boolean handleRequest( InputStream JavaDoc is, OutputStream JavaDoc os, String JavaDoc ip )
293         throws IOException JavaDoc
294     {
295         // We only support the GET method and know nothing of headers so this is easy.
296
// The first line of the request contains the requested url along with any
297
// and all encoded variables. All of the following lines until a pair of
298
// Line feeds are headers and can be skipped for now.
299

300         // Read the input header
301
BufferedReader JavaDoc r = new BufferedReader JavaDoc( new InputStreamReader JavaDoc( is ) );
302         String JavaDoc request = r.readLine();
303         if ( request == null )
304         {
305             // EOF
306
return false;
307         }
308             
309         String JavaDoc referrer = "-";
310         String JavaDoc userAgent = "-";
311         String JavaDoc host = null;
312         int requestBytes = request.getBytes().length + 1;
313         try
314         {
315             // Read any headers until we get a blank line
316
String JavaDoc header;
317             do
318             {
319                 header = r.readLine();
320                 getLogger().debug( "Header: " + header );
321                 if ( header != null )
322                 {
323                     if ( header.startsWith( "User-Agent: " ) )
324                     {
325                         userAgent = header.substring( 12 );
326                     }
327                     else if ( header.startsWith( "Referer: " ) )
328                     {
329                         referrer = header.substring( 9 );
330                     }
331                     else if ( header.startsWith( "Host: " ) )
332                     {
333                         host = header.substring( 6 );
334                     }
335                     
336                     requestBytes += header.getBytes().length + 1;
337                 }
338             }
339             while ( ( header != null ) && ( header.length() > 0 ) );
340         }
341         finally
342         {
343             if ( requestBytes > 0 )
344             {
345                 m_instrumentRequestBytes.increment( requestBytes );
346             }
347         }
348         
349         if ( getLogger().isDebugEnabled() )
350         {
351             getLogger().debug( "got request: " + request + " : "
352                 + Thread.currentThread().getName() );
353         }
354         
355         Throwable JavaDoc error = null;
356         
357         // Get the actual output stream so we can write the response.
358
ByteArrayOutputStream JavaDoc hbos = new ByteArrayOutputStream JavaDoc();
359         PrintWriter JavaDoc out = new PrintWriter JavaDoc( hbos );
360         
361         String JavaDoc method = "ERROR";
362         String JavaDoc url = "";
363         
364         // Parse the header to make sure it is valid.
365
StringTokenizer JavaDoc st = new StringTokenizer JavaDoc( request, " " );
366         if ( st.countTokens() == 3 )
367         {
368             method = st.nextToken();
369             url = st.nextToken();
370             String JavaDoc version = st.nextToken();
371             
372             if ( method.equals( "GET" ) && version.startsWith( "HTTP/" ) )
373             {
374                 // Extract the path and parameters from the request.
375
String JavaDoc path;
376                 String JavaDoc query = null;
377                 int pos = url.indexOf( '?' );
378                 if ( pos > 0 )
379                 {
380                     path = url.substring( 0, pos );
381                     
382                     if ( pos < url.length() - 1 )
383                     {
384                         query = url.substring( pos + 1 );
385                     }
386                 }
387                 else
388                 {
389                     path = url;
390                 }
391
392                 // We now have the path and the params.
393
// Look for an HTTPURLHandler which which maps to the path. Look in
394
// order until one is found.
395

396                 HTTPURLHandler[] handlers = getHandlers();
397                 for ( int i = 0; i < handlers.length; i++ )
398                 {
399                     HTTPURLHandler handler = handlers[i];
400                     
401                     //getLogger().debug( "Test: '" + path + "' starts with '" + handler.getPath() + "'" );
402
if ( path.startsWith( handler.getPath() ) )
403                     {
404                         // We found it.
405
//getLogger().debug( " => Matched." );
406

407                         // Decode the query string
408
Map JavaDoc params = new HashMap JavaDoc();
409                         if ( query != null )
410                         {
411                             //getLogger().debug( " Raw Query: " + query );
412
decodeQuery( params, query, handler.getEncoding() );
413                         }
414         
415                         if ( getLogger().isDebugEnabled() )
416                         {
417                             getLogger().debug( "Request Path: " + path );
418                             getLogger().debug( " Parameters: " + params.toString() );
419                         }
420                         
421                         m_instrumentRequests.increment();
422                         
423                         // Create a ByteArrayOutputStream that will be used to get the total number
424
// bytes that will be written in the response. This is necessary to set
425
// the content length in the return headers.
426
ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
427                         boolean ok;
428                         // Handle the URL
429
try
430                         {
431                             handler.handleRequest( path, params, bos );
432                             
433                             ok = true;
434                         }
435                         catch ( HTTPRedirect e )
436                         {
437                             // To avoid breaking some clients (including some versions of Java)
438
// we need to only send redirects to absolute URLs. We can only do
439
// this if the client gave us a Host: header to work with however.
440
String JavaDoc redirectPath = e.getPath();
441                             if ( ( host != null ) && ( redirectPath.indexOf( "://" ) < 0 ) )
442                             {
443                                 // No protocol so add it.
444
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
445                                 sb.append( "http://" );
446                                 sb.append( host );
447                                 if ( redirectPath.startsWith( "." ) )
448                                 {
449                                     // Relative path, so we need to give it a starting point.
450
int slashPos = path.lastIndexOf( '/' );
451                                     String JavaDoc subpath;
452                                     if ( slashPos > 0 )
453                                     {
454                                         subpath = path.substring( 0, slashPos + 1 );
455                                     }
456                                     else
457                                     {
458                                         subpath = "/";
459                                     }
460                                     sb.append( subpath );
461                                 }
462                                 else if ( !redirectPath.startsWith( "/" ) )
463                                 {
464                                     sb.append( "/" );
465                                 }
466                                 sb.append( redirectPath );
467                                 
468                                 redirectPath = sb.toString();
469                             }
470                             
471                             if ( getLogger().isDebugEnabled() )
472                             {
473                                 if ( redirectPath.equals( e.getPath() ) )
474                                 {
475                                     getLogger().debug( "Redirect to: " + redirectPath );
476                                 }
477                                 else
478                                 {
479                                     getLogger().debug(
480                                         "Redirect to: " + e.getPath() + " -> " + redirectPath );
481                                 }
482                             }
483                             
484                             byte[] contents = ( "<html><head><title>302 Found</title></head><body>"
485                                 + "The document has moved <a HREF='" + redirectPath + "'>here</a>"
486                                 + "</body></html>" ).getBytes( handler.getEncoding() );
487                                 
488                             // Write the response.
489
out.println( "HTTP/1.1 302 Found" ); // MOVED_TEMP
490
out.println( "Date: " + new Date JavaDoc() );
491                             out.println( "Server: Avalon Instrument Manager HTTP Connector" );
492                             out.println( "Content-Length: " + contents.length );
493                             out.println( "Location: " + redirectPath );
494                             out.println( "Keep-Alive: timeout=" + ( getSoTimeout() / 1000 ) );
495                             out.println( "Connection: Keep-Alive" );
496                             out.println( "Content-Type: " + handler.getContentType() );
497                             // Make sure that no caching is done by the client
498
out.println( "Pragma: no-cache" );
499                             out.println( "Expires: Thu, 01 Jan 1970 00:00:00 GMT" );
500                             out.println( "Cache-Control: no-cache" );
501                             
502                             // Terminate the Headers.
503
out.println( "" );
504                             
505                             // Write the contents of the headers.
506
out.flush();
507                             byte[] responseBytes = hbos.toByteArray();
508                             os.write( responseBytes );
509                             
510                             // Write out the actual data directly to the stream.
511
os.write( contents, 0, contents.length );
512                             
513                             // Flush the stream and we are done.
514
os.flush();
515                             
516                             // Record the total number of bytes sent to the client.
517
m_instrumentResponseBytes.increment(
518                                 responseBytes.length + contents.length );
519                             
520                             // Log the request.
521
logAccessEvent(
522                                 ip, method, url, 302, contents.length, referrer, userAgent );
523                             
524                             // Do not close the output stream as it may be reused.
525

526                             return true;
527                         }
528                         catch ( Throwable JavaDoc t )
529                         {
530                             // Error
531
error = t;
532                             ok = false;
533                         }
534                         
535                         if ( ok )
536                         {
537                             byte[] contents = bos.toByteArray();
538                             
539                             // Write the response.
540
out.println( "HTTP/1.1 200 OK" );
541                             out.println( "Date: " + new Date JavaDoc() );
542                             out.println( "Server: Avalon Instrument Manager HTTP Connector" );
543                             out.println( "Content-Length: " + contents.length );
544                             out.println( "Keep-Alive: timeout=" + ( getSoTimeout() / 1000 ) );
545                             out.println( "Connection: Keep-Alive" );
546                             out.println( "Content-Type: " + handler.getContentType() );
547                             // Make sure that no caching is done by the client
548
out.println( "Pragma: no-cache" );
549                             out.println( "Expires: Thu, 01 Jan 1970 00:00:00 GMT" );
550                             out.println( "Cache-Control: no-cache" );
551                             
552                             // Terminate the Headers.
553
out.println( "" );
554                             
555                             // Write the contents of the headers.
556
out.flush();
557                             byte[] responseBytes = hbos.toByteArray();
558                             os.write( responseBytes );
559                             
560                             // Write out the actual data directly to the stream.
561
os.write( contents, 0, contents.length );
562                             
563                             // Flush the stream and we are done.
564
os.flush();
565                             
566                             // Record the total number of bytes sent to the client.
567
m_instrumentResponseBytes.increment(
568                                 responseBytes.length + contents.length );
569                             
570                             // Log the request.
571
logAccessEvent( ip, method, url, 200, contents.length, referrer, userAgent );
572                             
573                             // Do not close the output stream as it may be reused.
574

575                             return true;
576                         }
577                         else
578                         {
579                             // Break out of the for loop.
580
break;
581                         }
582                     }
583                 }
584             }
585         }
586         
587         // If we get here then the request failed. Always return 404 for now.
588
// Write the response.
589
out.println( "HTTP/1.1 404 Not Found" );
590         out.println( "Date: " + new Date JavaDoc() );
591         out.println( "Server: Avalon Instrument Manager HTTP Connector" );
592         out.println( "Content-Type: text/plain; charset=UTF-8" );
593         out.println( "" );
594         out.println( "The Requested page does not exist" );
595         if ( error != null )
596         {
597             out.println( "---" );
598             if ( error instanceof FileNotFoundException JavaDoc )
599             {
600                 out.println( error.getMessage() );
601             }
602             else
603             {
604                 getLogger().error( "Error servicing request.", error );
605                 error.printStackTrace( out );
606             }
607         }
608         
609         // Write the contents of the headers.
610
out.flush();
611         byte[] responseBytes = hbos.toByteArray();
612         os.write( responseBytes );
613         os.flush();
614         
615         // Record the total number of bytes sent to the client.
616
m_instrumentResponseBytes.increment( responseBytes.length );
617         
618         // Log the request.
619
logAccessEvent( ip, method, url, 404, 0, referrer, userAgent );
620         
621         return false;
622     }
623     
624     public void setParameter( Map JavaDoc params, String JavaDoc param, String JavaDoc value )
625     {
626         Object JavaDoc old = params.get( param );
627         if ( old == null )
628         {
629             params.put( param, value );
630         }
631         else
632         {
633             if ( old instanceof String JavaDoc )
634             {
635                 List JavaDoc list = new ArrayList JavaDoc();
636                 list.add( old );
637                 list.add( value );
638                 params.put( param, list );
639             }
640             else
641             {
642                 List JavaDoc list = (List JavaDoc)old;
643                 list.add( value );
644             }
645         }
646     }
647     
648     private void decodeParameter( Map JavaDoc params, String JavaDoc pair, String JavaDoc encoding )
649     {
650         int pos = pair.indexOf( '=' );
651         if ( pos > 0 )
652         {
653             try
654             {
655                 // Starting with Java 1.4, encode takes an encoding, but this needs to
656
// work with 1.3. Use our own version.
657
String JavaDoc param = URLCoder.decode( pair.substring( 0, pos ), encoding );
658                 
659                 String JavaDoc value;
660                 if ( pos < pair.length() - 1 )
661                 {
662                     value = URLCoder.decode( pair.substring( pos + 1 ), encoding );
663                 }
664                 else
665                 {
666                     value = "";
667                 }
668                 
669                 setParameter( params, param, value );
670             }
671             catch ( UnsupportedEncodingException JavaDoc e )
672             {
673                 throw new IllegalArgumentException JavaDoc( "Unknown encoding: " + e.toString() );
674             }
675         }
676     }
677     
678     private void decodeQuery( Map JavaDoc params, String JavaDoc query, String JavaDoc encoding )
679     {
680         StringTokenizer JavaDoc st = new StringTokenizer JavaDoc( query, "&" );
681         while ( st.hasMoreTokens() )
682         {
683             decodeParameter( params, st.nextToken(), encoding );
684         }
685     }
686     
687     private HTTPURLHandler[] getHandlers()
688     {
689         HTTPURLHandler[] handlers = m_handlerArray;
690         if ( handlers == null )
691         {
692             synchronized( m_handlers )
693             {
694                 handlers = new HTTPURLHandler[ m_handlers.size() ];
695                 m_handlers.toArray( handlers );
696                 m_handlerArray = handlers;
697             }
698         }
699         
700         return handlers;
701     }
702 }
703
704
Popular Tags