Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/jk/common/HandlerRequest.java


1   /*
2    *  Copyright 1999-2005 The Apache Software Foundation
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package org.apache.jk.common;
18  
19  import java.io.File;
20  import java.io.FileOutputStream;
21  import java.io.IOException;
22  import java.io.CharConversionException;
23  import java.net.InetAddress;
24  import java.util.Properties;
25  
26  import org.apache.coyote.Request;
27  import org.apache.coyote.RequestInfo;
28  import org.apache.coyote.Response;
29  import org.apache.coyote.Constants;
30  import org.apache.jk.core.JkHandler;
31  import org.apache.jk.core.Msg;
32  import org.apache.jk.core.MsgContext;
33  import org.apache.jk.core.WorkerEnv;
34  import org.apache.jk.core.JkChannel;
35  import org.apache.tomcat.util.buf.ByteChunk;
36  import org.apache.tomcat.util.buf.CharChunk;
37  import org.apache.tomcat.util.buf.HexUtils;
38  import org.apache.tomcat.util.buf.MessageBytes;
39  import org.apache.tomcat.util.http.MimeHeaders;
40  import org.apache.tomcat.util.net.SSLSupport;
41  import org.apache.tomcat.util.threads.ThreadWithAttributes;
42  
43  /**
44   * Handle messages related with basic request information.
45   *
46   * This object can handle the following incoming messages:
47   * - "FORWARD_REQUEST" input message ( sent when a request is passed from the
48   *   web server )
49   * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in
50   *   response to GET_BODY_CHUNK )
51   *
52   * It can handle the following outgoing messages:
53   * - SEND_HEADERS. Pass the status code and headers.
54   * - SEND_BODY_CHUNK. Send a chunk of body
55   * - GET_BODY_CHUNK. Request a chunk of body data
56   * - END_RESPONSE. Notify the end of a request processing.
57   *
58   * @author Henri Gomez [hgomez@apache.org]
59   * @author Dan Milstein [danmil@shore.net]
60   * @author Keith Wannamaker [Keith@Wannamaker.org]
61   * @author Costin Manolache
62   */
63  public class HandlerRequest extends JkHandler
64  {
65      private static org.apache.commons.logging.Log log=
66          org.apache.commons.logging.LogFactory.getLog( HandlerRequest.class );
67  
68      /*
69       * Note for Host parsing.
70       */
71      public static final int HOSTBUFFER = 10;
72  
73      /**
74       * Thread lock.
75       */
76      private static Object lock = new Object();
77  
78      private HandlerDispatch dispatch;
79      private String ajpidDir="conf";
80      
81  
82      public HandlerRequest() {
83      }
84  
85      public void init() {
86          dispatch=(HandlerDispatch)wEnv.getHandler( "dispatch" );
87          if( dispatch != null ) {
88              // register incoming message handlers
89              dispatch.registerMessageType( AjpConstants.JK_AJP13_FORWARD_REQUEST,
90                                            "JK_AJP13_FORWARD_REQUEST",
91                                            this, null); // 2
92              
93              dispatch.registerMessageType( AjpConstants.JK_AJP13_SHUTDOWN,
94                                            "JK_AJP13_SHUTDOWN",
95                                            this, null); // 7
96              
97              dispatch.registerMessageType( AjpConstants.JK_AJP13_CPING_REQUEST,
98                                            "JK_AJP13_CPING_REQUEST",
99                                             this, null); // 10
100             dispatch.registerMessageType( HANDLE_THREAD_END,
101                                          "HANDLE_THREAD_END",
102                                          this, null);
103             // register outgoing messages handler
104             dispatch.registerMessageType( AjpConstants.JK_AJP13_SEND_BODY_CHUNK, // 3
105                                           "JK_AJP13_SEND_BODY_CHUNK",
106                                           this,null );
107         }
108 
109         tmpBufNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "tmpBuf" );
110         secretNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "secret" );
111         
112         if( next==null )
113             next=wEnv.getHandler( "container" );
114         if( log.isDebugEnabled() )
115             log.debug( "Container handler " + next + " " + next.getName() +
116                        " " + next.getClass().getName());
117 
118         // should happen on start()
119         generateAjp13Id();
120     }
121 
122     public void setSecret( String s ) {
123         requiredSecret=s;
124     }
125 
126     public void setUseSecret( boolean b ) {
127         if(b) {
128             requiredSecret=Double.toString(Math.random());
129         }
130     }
131 
132     public void setDecodedUri( boolean b ) {
133         decoded=b;
134     }
135 
136     public boolean isTomcatAuthentication() {
137         return tomcatAuthentication;
138     }
139 
140     public void setShutdownEnabled(boolean se) {
141         shutdownEnabled = se;
142     }
143 
144     public boolean getShutdownEnabled() {
145         return shutdownEnabled;
146     }
147 
148     public void setTomcatAuthentication(boolean newTomcatAuthentication) {
149         tomcatAuthentication = newTomcatAuthentication;
150     }
151     
152     public void setAjpidDir( String path ) {
153         if( "".equals( path ) ) path=null;
154         ajpidDir=path;
155     }
156 
157     /**
158      * Set the flag to tell if we JMX register requests.
159      */
160     public void setRegisterRequests(boolean srr) {
161         registerRequests = srr;
162     }
163 
164     /**
165      * Get the flag to tell if we JMX register requests.
166      */
167     public boolean getRegisterRequests() {
168         return registerRequests;
169     }
170 
171     /**
172      * Set the flag to delay the initial body read
173      */
174     public void setDelayInitialRead(boolean dir) {
175   delayInitialRead = dir;
176     }
177 
178     /**
179      * Get the flag to tell if we delay the initial body read
180      */
181     public boolean getDelayInitialRead() {
182   return delayInitialRead;
183     }
184 
185     // -------------------- Ajp13.id --------------------
186 
187     private void generateAjp13Id() {
188         int portInt=8009; // tcpCon.getPort();
189         InetAddress address=null; // tcpCon.getAddress();
190 
191         if( requiredSecret == null )
192             return;
193         
194         File f1=new File( wEnv.getJkHome() );
195         File f2=new File( f1, "conf" );
196         
197         if( ! f2.exists() ) {
198             log.error( "No conf dir for ajp13.id " + f2 );
199             return;
200         }
201         
202         File sf=new File( f2, "ajp13.id");
203         
204         if( log.isDebugEnabled())
205             log.debug( "Using stop file: "+sf);
206 
207         try {
208             Properties props=new Properties();
209 
210             props.put( "port", Integer.toString( portInt ));
211             if( address!=null ) {
212                 props.put( "address", address.getHostAddress() );
213             }
214             if( requiredSecret !=null ) {
215                 props.put( "secret", requiredSecret );
216             }
217 
218             FileOutputStream stopF=new FileOutputStream( sf );
219             props.store( stopF, "Automatically generated, don't edit" );
220         } catch( IOException ex ) {
221             if(log.isDebugEnabled())
222                 log.debug( "Can't create stop file: "+sf,ex );
223         }
224     }
225     
226     // -------------------- Incoming message --------------------
227     private String requiredSecret=null;
228     private int secretNote;
229     private int tmpBufNote;
230 
231     private boolean decoded=true;
232     private boolean tomcatAuthentication=true;
233     private boolean registerRequests=true;
234     private boolean shutdownEnabled=false;
235     private boolean delayInitialRead = true;
236     
237     public int invoke(Msg msg, MsgContext ep ) 
238         throws IOException    {
239         int type=msg.getByte();
240         ThreadWithAttributes twa = null;
241         if (Thread.currentThread() instanceof ThreadWithAttributes) {
242             twa = (ThreadWithAttributes) Thread.currentThread();
243         }
244         Object control=ep.getControl();
245         MessageBytes tmpMB=(MessageBytes)ep.getNote( tmpBufNote );
246         if( tmpMB==null ) {
247             tmpMB= MessageBytes.newInstance();
248             ep.setNote( tmpBufNote, tmpMB);
249         }
250 
251         if( log.isDebugEnabled() )
252             log.debug( "Handling " + type );
253         
254         switch( type ) {
255         case AjpConstants.JK_AJP13_FORWARD_REQUEST:
256             try {
257                 if (twa != null) {
258                     twa.setCurrentStage(control, "JkDecode");
259                 }
260                 decodeRequest( msg, ep, tmpMB );
261                 if (twa != null) {
262                     twa.setCurrentStage(control, "JkService");
263                     twa.setParam(control,
264                                  ((Request)ep.getRequest()).unparsedURI());
265                 }
266             } catch( Exception ex ) {
267                 log.error( "Error decoding request ", ex );
268                 msg.dump( "Incomming message");
269                 return ERROR;
270             }
271 
272             if( requiredSecret != null ) {
273                 String epSecret=(String)ep.getNote( secretNote );
274                 if( epSecret==null || ! requiredSecret.equals( epSecret ) )
275                     return ERROR;
276             }
277             /* XXX it should be computed from request, by workerEnv */
278             if(log.isDebugEnabled() )
279                 log.debug("Calling next " + next.getName() + " " +
280                   next.getClass().getName());
281 
282             int err= next.invoke( msg, ep );
283             if (twa != null) {
284                 twa.setCurrentStage(control, "JkDone");
285             }
286 
287             if( log.isDebugEnabled() )
288                 log.debug( "Invoke returned " + err );
289             return err;
290         case AjpConstants.JK_AJP13_SHUTDOWN:
291             String epSecret=null;
292             if( msg.getLen() > 3 ) {
293                 // we have a secret
294                 msg.getBytes( tmpMB );
295                 epSecret=tmpMB.toString();
296             }
297             
298             if( requiredSecret != null &&
299                 requiredSecret.equals( epSecret ) ) {
300                 if( log.isDebugEnabled() )
301                     log.debug("Received wrong secret, no shutdown ");
302                 return ERROR;
303             }
304 
305             // XXX add isSameAddress check
306             JkChannel ch=ep.getSource();
307             if( !ch.isSameAddress(ep) ) {
308                 log.error("Shutdown request not from 'same address' ");
309                 return ERROR;
310             }
311 
312             if( !shutdownEnabled ) {
313                 log.warn("Ignoring shutdown request: shutdown not enabled");
314                 return ERROR;
315             }
316             // forward to the default handler - it'll do the shutdown
317             checkRequest(ep);
318             next.invoke( msg, ep );
319 
320             if(log.isInfoEnabled())
321                 log.info("Exiting");
322             System.exit(0);
323             
324             return OK;
325 
326             // We got a PING REQUEST, quickly respond with a PONG
327         case AjpConstants.JK_AJP13_CPING_REQUEST:
328             msg.reset();
329             msg.appendByte(AjpConstants.JK_AJP13_CPONG_REPLY);
330             ep.getSource().send( msg, ep );
331             ep.getSource().flush( msg, ep ); // Server needs to get it
332             return OK;
333 
334         case HANDLE_THREAD_END:
335             return OK;
336 
337         default:
338             if(log.isInfoEnabled())
339                 log.info("Unknown message " + type);
340         }
341 
342         return OK;
343     }
344 
345     static int count = 0;
346 
347     private Request checkRequest(MsgContext ep) {
348         Request req=ep.getRequest();
349         if( req==null ) {
350             req=new Request();
351             Response res=new Response();
352             req.setResponse(res);
353             ep.setRequest( req );
354             if( registerRequests ) {
355                 synchronized(lock) {
356                     ep.getSource().registerRequest(req, ep, count++);
357                 }
358             }
359         }
360         return req;
361     }
362 
363     private int decodeRequest( Msg msg, MsgContext ep, MessageBytes tmpMB )
364         throws IOException    {
365         // FORWARD_REQUEST handler
366         Request req = checkRequest(ep);
367 
368         RequestInfo rp = req.getRequestProcessor();
369         rp.setStage(Constants.STAGE_PARSE);
370         MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
371         if(tmpMB2 != null) {
372             tmpMB2.recycle();
373         }
374         req.setStartTime(System.currentTimeMillis());
375         
376         // Translate the HTTP method code to a String.
377         byte methodCode = msg.getByte();
378         if (methodCode != AjpConstants.SC_M_JK_STORED) {
379             String mName=AjpConstants.methodTransArray[(int)methodCode - 1];
380             req.method().setString(mName);
381         }
382 
383         msg.getBytes(req.protocol()); 
384         msg.getBytes(req.requestURI());
385 
386         msg.getBytes(req.remoteAddr());
387         msg.getBytes(req.remoteHost());
388         msg.getBytes(req.localName());
389         req.setLocalPort(msg.getInt());
390 
391         boolean isSSL = msg.getByte() != 0;
392         if( isSSL ) {
393             // XXX req.setSecure( true );
394             req.scheme().setString("https");
395         }
396 
397         decodeHeaders( ep, msg, req, tmpMB );
398 
399         decodeAttributes( ep, msg, req, tmpMB );
400 
401         rp.setStage(Constants.STAGE_PREPARE);
402         MessageBytes valueMB = req.getMimeHeaders().getValue("host");
403         parseHost(valueMB, req);
404         // set cookies on request now that we have all headers
405         req.getCookies().setHeaders(req.getMimeHeaders());
406 
407         // Check to see if there should be a body packet coming along
408         // immediately after
409         int cl=req.getContentLength();
410         if(cl > 0) {
411             JkInputStream jkIS = ep.getInputStream();
412             jkIS.setIsReadRequired(true);
413             if(!delayInitialRead) {
414                 jkIS.receive();
415             }
416         }
417     
418         if (log.isTraceEnabled()) {
419             log.trace(req.toString());
420          }
421 
422         return OK;
423     }
424         
425     private int decodeAttributes( MsgContext ep, Msg msg, Request req,
426                                   MessageBytes tmpMB) {
427         boolean moreAttr=true;
428 
429         while( moreAttr ) {
430             byte attributeCode=msg.getByte();
431             if( attributeCode == AjpConstants.SC_A_ARE_DONE )
432                 return 200;
433 
434             /* Special case ( XXX in future API make it separate type !)
435              */
436             if( attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE ) {
437                 // Bug 1326: it's an Integer.
438                 req.setAttribute(SSLSupport.KEY_SIZE_KEY,
439                                  new Integer( msg.getInt()));
440                //Integer.toString(msg.getInt()));
441             }
442 
443             if( attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE ) {
444                 // 2 strings ???...
445                 msg.getBytes( tmpMB );
446                 String n=tmpMB.toString();
447                 msg.getBytes( tmpMB );
448                 String v=tmpMB.toString();
449                 req.setAttribute(n, v );
450                 if(log.isTraceEnabled())
451                     log.trace("jk Attribute set " + n + "=" + v);
452             }
453 
454 
455             // 1 string attributes
456             switch(attributeCode) {
457             case AjpConstants.SC_A_CONTEXT      :
458                 msg.getBytes( tmpMB );
459                 // nothing
460                 break;
461                 
462             case AjpConstants.SC_A_SERVLET_PATH :
463                 msg.getBytes( tmpMB );
464                 // nothing 
465                 break;
466                 
467             case AjpConstants.SC_A_REMOTE_USER  :
468                 if( tomcatAuthentication ) {
469                     // ignore server
470                     msg.getBytes( tmpMB );
471                 } else {
472                     msg.getBytes(req.getRemoteUser());
473                 }
474                 break;
475                 
476             case AjpConstants.SC_A_AUTH_TYPE    :
477                 if( tomcatAuthentication ) {
478                     // ignore server
479                     msg.getBytes( tmpMB );
480                 } else {
481                     msg.getBytes(req.getAuthType());
482                 }
483                 break;
484                 
485             case AjpConstants.SC_A_QUERY_STRING :
486                 msg.getBytes(req.queryString());
487                 break;
488                 
489             case AjpConstants.SC_A_JVM_ROUTE    :
490                 msg.getBytes(req.instanceId());
491                 break;
492                 
493             case AjpConstants.SC_A_SSL_CERT     :
494                 req.scheme().setString( "https" );
495                 // Transform the string into certificate.
496                 MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
497                 if(tmpMB2 == null) {
498                     tmpMB2 = MessageBytes.newInstance();
499                     req.setNote(WorkerEnv.SSL_CERT_NOTE, tmpMB2);
500                 }
501                 // SSL certificate extraction is costy, moved to JkCoyoteHandler
502                 msg.getBytes(tmpMB2);
503                 break;
504                 
505             case AjpConstants.SC_A_SSL_CIPHER   :
506                 req.scheme().setString( "https" );
507                 msg.getBytes(tmpMB);
508                 req.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
509                                  tmpMB.toString());
510                 break;
511                 
512             case AjpConstants.SC_A_SSL_SESSION  :
513                 req.scheme().setString( "https" );
514                 msg.getBytes(tmpMB);
515                 req.setAttribute(SSLSupport.SESSION_ID_KEY, 
516                                   tmpMB.toString());
517                 break;
518                 
519             case AjpConstants.SC_A_SECRET  :
520                 msg.getBytes(tmpMB);
521                 String secret=tmpMB.toString();
522                 if(log.isTraceEnabled())
523                     log.trace("Secret: " + secret );
524                 // endpoint note
525                 ep.setNote( secretNote, secret );
526                 break;
527                 
528             case AjpConstants.SC_A_STORED_METHOD:
529                 msg.getBytes(req.method()); 
530                 break;
531                 
532             default:
533                 break; // ignore, we don't know about it - backward compat
534             }
535         }
536         return 200;
537     }
538     
539     private void decodeHeaders( MsgContext ep, Msg msg, Request req,
540                                 MessageBytes tmpMB ) {
541         // Decode headers
542         MimeHeaders headers = req.getMimeHeaders();
543 
544         int hCount = msg.getInt();
545         for(int i = 0 ; i < hCount ; i++) {
546             String hName = null;
547 
548             // Header names are encoded as either an integer code starting
549             // with 0xA0, or as a normal string (in which case the first
550             // two bytes are the length).
551             int isc = msg.peekInt();
552             int hId = isc & 0xFF;
553 
554             MessageBytes vMB=null;
555             isc &= 0xFF00;
556             if(0xA000 == isc) {
557                 msg.getInt(); // To advance the read position
558                 hName = AjpConstants.headerTransArray[hId - 1];
559                 vMB=headers.addValue( hName );
560             } else {
561                 // reset hId -- if the header currently being read
562                 // happens to be 7 or 8 bytes long, the code below
563                 // will think it's the content-type header or the
564                 // content-length header - SC_REQ_CONTENT_TYPE=7,
565                 // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
566                 // behaviour.  see bug 5861 for more information.
567                 hId = -1;
568                 msg.getBytes( tmpMB );
569                 ByteChunk bc=tmpMB.getByteChunk();
570                 vMB=headers.addValue( bc.getBuffer(),
571                                       bc.getStart(), bc.getLength() );
572             }
573 
574             msg.getBytes(vMB);
575 
576             if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH ||
577                 (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
578                 // just read the content-length header, so set it
579                 req.setContentLength( vMB.getInt() );
580             } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE ||
581                 (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
582                 // just read the content-type header, so set it
583                 ByteChunk bchunk = vMB.getByteChunk();
584                 req.contentType().setBytes(bchunk.getBytes(),
585                                            bchunk.getOffset(),
586                                            bchunk.getLength());
587             }
588         }
589     }
590 
591     /**
592      * Parse host.
593      */
594     private void parseHost(MessageBytes valueMB, Request request) 
595         throws IOException {
596 
597         if (valueMB == null || valueMB.isNull()) {
598             // HTTP/1.0
599             // Default is what the socket tells us. Overriden if a host is 
600             // found/parsed
601             request.setServerPort(request.getLocalPort());
602             request.serverName().duplicate(request.localName());
603             return;
604         }
605 
606         ByteChunk valueBC = valueMB.getByteChunk();
607         byte[] valueB = valueBC.getBytes();
608         int valueL = valueBC.getLength();
609         int valueS = valueBC.getStart();
610         int colonPos = -1;
611         CharChunk hostNameC = (CharChunk)request.getNote(HOSTBUFFER);
612         if(hostNameC == null) {
613             hostNameC = new CharChunk(valueL);
614             request.setNote(HOSTBUFFER, hostNameC);
615         }
616         hostNameC.recycle();
617 
618         boolean ipv6 = (valueB[valueS] == '[');
619         boolean bracketClosed = false;
620         for (int i = 0; i < valueL; i++) {
621             char b = (char) valueB[i + valueS];
622             hostNameC.append(b);
623             if (b == ']') {
624                 bracketClosed = true;
625             } else if (b == ':') {
626                 if (!ipv6 || bracketClosed) {
627                     colonPos = i;
628                     break;
629                 }
630             }
631         }
632 
633         if (colonPos < 0) {
634             if (request.scheme().equalsIgnoreCase("https")) {
635                 // 80 - Default HTTTP port
636                 request.setServerPort(443);
637             } else {
638                 // 443 - Default HTTPS port
639                 request.setServerPort(80);
640             }
641             request.serverName().setChars(hostNameC.getChars(), 
642                                           hostNameC.getStart(), 
643                                           hostNameC.getLength());
644         } else {
645 
646             request.serverName().setChars(hostNameC.getChars(), 
647                                           hostNameC.getStart(), colonPos);
648 
649             int port = 0;
650             int mult = 1;
651             for (int i = valueL - 1; i > colonPos; i--) {
652                 int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
653                 if (charValue == -1) {
654                     // Invalid character
655                     throw new CharConversionException("Invalid char in port: " + valueB[i + valueS]); 
656                 }
657                 port = port + (charValue * mult);
658                 mult = 10 * mult;
659             }
660             request.setServerPort(port);
661 
662         }
663 
664     }
665 
666 }