alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*   
 *  Copyright 1999-2004 The Apache Sofware Foundation.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.tomcat.modules.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;

import org.apache.tomcat.core.Context;
import org.apache.tomcat.core.ContextManager;
import org.apache.tomcat.core.Request;
import org.apache.tomcat.core.Response;
import org.apache.tomcat.util.buf.DateTool;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.HttpMessages;
import org.apache.tomcat.util.log.Log;
import org.apache.tomcat.util.net.SSLSupport;
import org.apache.tomcat.util.net.TcpConnection;
import org.apache.tomcat.util.net.TcpConnectionHandler;

/** Standalone http.
 *
 *  Connector properties:
 *  - secure - will load a SSL socket factory and act as https server
 *
 *  Properties passed to the net layer:
 *  - timeout
 *  - backlog
 *  - address
 *  - port
 * Thread pool properties:
 *  - minSpareThreads
 *  - maxSpareThreads
 *  - maxThreads
 *  - poolOn
 * Properties for HTTPS:
 *  - keystore - certificates - default to ~/.keystore
 *  - keypass - password
 *  - clientauth - true if the server should authenticate the client using certs
 * Properties for HTTP:
 *  - reportedname - name of server sent back to browser (security purposes)
 */
public class Http10Interceptor extends PoolTcpConnector
    implements  TcpConnectionHandler
{
    private int	timeout = 300000;	// 5 minutes as in Apache HTTPD server
    private String reportedname;
    private int socketCloseDelay=-1;

    public Http10Interceptor() {
	super();
        super.setSoLinger( 100 );
	// defaults:
	this.setPort( 8080 );
    }

    // -------------------- PoolTcpConnector --------------------

    protected void localInit() throws Exception {
	ep.setConnectionHandler( this );
    }

    // -------------------- Attributes --------------------
    public void setTimeout( int timeouts ) {
	timeout = timeouts * 1000;
    }
    public void setReportedname( String reportedName) {
    reportedname = reportedName;
    }

    public void setSocketCloseDelay( int d ) {
        socketCloseDelay=d;
    }

    public void setProperty( String prop, String value ) {
        setAttribute( prop, value );
    }

    // -------------------- Handler implementation --------------------
    public void setServer( Object o ) {
	this.cm=(ContextManager)o;
    }
    
    public Object[] init() {
	Object thData[]=new Object[3];
	HttpRequest reqA=new HttpRequest();
	HttpResponse resA=new HttpResponse();
	if (reportedname != null)
	    resA.setReported(reportedname);
	cm.initRequest( reqA, resA );
	thData[0]=reqA;
	thData[1]=resA;
	thData[2]=null;
	return  thData;
    }

    public void processConnection(TcpConnection connection, Object thData[]) {
	Socket socket=null;
	HttpRequest reqA=null;
	HttpResponse resA=null;

	try {
	    reqA=(HttpRequest)thData[0];
	    resA=(HttpResponse)thData[1];

	    socket=connection.getSocket();
		socket.setSoTimeout(timeout);

	    reqA.setSocket( socket );
	    resA.setSocket( socket );

	    reqA.readNextRequest(resA);
	    if( secure ) {
		reqA.scheme().setString( "https" );
 
 		// Load up the SSLSupport class
		if(sslImplementation != null)
		    reqA.setSSLSupport(sslImplementation.getSSLSupport(socket));
	    }
	    
	    cm.service( reqA, resA );

            // If unread input arrives after the shutdownInput() call
            // below and before or during the socket.close(), an error
            // may be reported to the client.  To help troubleshoot this
            // type of error, provide a configurable delay to give the
            // unread input time to arrive so it can be successfully read
            // and discarded by shutdownInput().
            if( socketCloseDelay >= 0 ) {
                try {
                    Thread.sleep(socketCloseDelay);
                } catch (InterruptedException ie) { /* ignore */ }
            }

        // XXX didn't honor HTTP/1.0 KeepAlive, should be fixed
	    TcpConnection.shutdownInput( socket );
	}
	catch(java.net.SocketException e) {
	    // SocketExceptions are normal
	    log( "SocketException reading request, ignored", null,
		 Log.INFORMATION);
	    log( "SocketException reading request:", e, Log.DEBUG);
	}
	catch (java.io.InterruptedIOException ioe) {
		// We have armed a timeout on read as does apache httpd server.
		// Just to avoid staying with inactive connection
		// BUG#1006
		ioe.printStackTrace();
		log( "Timeout reading request, aborting", ioe, Log.ERROR);
	}
	catch (java.io.IOException e) {
	    // IOExceptions are normal 
	    log( "IOException reading request, ignored", null,
		 Log.INFORMATION);
	    log( "IOException reading request:", e, Log.DEBUG);
	}
	// Future developers: if you discover any other
	// rare-but-nonfatal exceptions, catch them here, and log as
	// above.
	catch (Throwable e) {
	    // any other exception or error is odd. Here we log it
	    // with "ERROR" level, so it will show up even on
	    // less-than-verbose logs.
	    e.printStackTrace();
	    log( "Error reading request, ignored", e, Log.ERROR);
	} 
	finally {
	    // recycle kernel sockets ASAP
        // XXX didn't honor HTTP/1.0 KeepAlive, should be fixed
	    try { if (socket != null) socket.close (); }
	    catch (IOException e) { /* ignore */ }
        }
    }

    /** Internal constants for getInfo */ 
    private static final int GET_OTHER = 0;
    private static final int GET_CIPHER_SUITE = 1;
    private static final int GET_PEER_CERTIFICATE_CHAIN = 2;

     /**
       getInfo calls for SSL data
 
       @return the requested data
     */
     public Object getInfo( Context ctx, Request request,
 			   int id, String key ) {
       // The following code explicitly assumes that the only
       // attributes hand;ed here are HTTP. If you change that
       // you MUST change the test for sslSupport==null --EKR
 
       if (key != null) {
         int infoRequested = GET_OTHER;
         if(key.equals("javax.servlet.request.cipher_suite"))
           infoRequested = GET_CIPHER_SUITE;
         else if(key.equals("javax.servlet.request.X509Certificate"))
           infoRequested = GET_PEER_CERTIFICATE_CHAIN;

         if(infoRequested != GET_OTHER) {
           HttpRequest httpReq;

           try {
             httpReq=(HttpRequest)request;
           } catch (ClassCastException e){
             return null;
           }
 
           if (httpReq!=null && httpReq.sslSupport!=null){
             try {
               switch (infoRequested) {
                 case GET_CIPHER_SUITE:
                   return httpReq.sslSupport.getCipherSuite();
                 case GET_PEER_CERTIFICATE_CHAIN:
                   return httpReq.sslSupport.getPeerCertificateChain();
               }
             } catch (Exception e){
               log("Exception getting SSL attribute " + key,e,Log.WARNING);
               return null;
             }
           } // if req != null
         } // if asking for ssl attribute
       } // if key != null
       return super.getInfo(ctx,request,id,key);
     } // getInfo
}

class HttpRequest extends Request {
    Http10 http=new Http10();
    private boolean moreRequests = false;
    Socket socket;
    SSLSupport sslSupport=null;
    
    public HttpRequest() {
        super();

        // recycle these to remove the defaults
        remoteAddrMB.recycle();
        remoteHostMB.recycle();
    }

    public void recycle() {
	super.recycle();
	if( http!=null) http.recycle();
        // recycle these to remove the defaults
        remoteAddrMB.recycle();
        remoteHostMB.recycle();
	sslSupport=null;
    }

    public void setSocket(Socket socket) throws IOException {
	http.setSocket( socket );
	this.socket=socket;
    }

    public Socket getSocket() {
        return socket;
    }

    public int doRead() throws IOException {
	if( available == 0 ) 
	    return -1;
	// #3745
	// if available == -1: unknown length, we'll read until end of stream.
	if( available!= -1 )
	    available--;
	return http.doRead();
    }

    public int doRead(byte[] b, int off, int len) throws IOException {
	if( available == 0 )
	    return -1;
	// if available == -1: unknown length, we'll read until end of stream.
	int rd=http.doRead( b, off, len );
	if( rd==-1) {
	    available=0;
	    return -1;
	}
	if( available!= -1 )
	    available -= rd;
	return rd;
    }
    

    public void readNextRequest(Response response) throws IOException {
	int status=http.processRequestLine( methodMB, uriMB,queryMB, protoMB );
	// XXX remove this after we swich to MB
	// 	method=methodMB.toString();
	// 	requestURI=uriMB.toString();
	// 	queryString=queryMB.toString();
	// 	protocol=protoMB.toString();
	
	if( status > 200 ) {
	    response.setStatus( status );
	    return;
	}

	// for 0.9, we don't have headers!
	if (! protoMB.equals("")) {
	    // all HTTP versions with protocol also have headers
	    // ( 0.9 has no HTTP/0.9 !)
	    status=http.readHeaders( headers  );
	    if( status >200 ) {
		response.setStatus( status );
		return;
	    }
	}

	// XXX detect for real whether or not we have more requests
	// coming
	moreRequests = false;
    }

    // -------------------- override special methods

    public MessageBytes remoteAddr() {
	// WARNING: On some linux configurations, this call may get you in
	// trubles... Big trubles...
	if( remoteAddrMB.isNull() ) {
	    remoteAddrMB.setString(socket.getInetAddress().getHostAddress());
	}
	return remoteAddrMB;
    }

    public MessageBytes remoteHost() {
	if( remoteHostMB.isNull() ) {
	    remoteHostMB.setString( socket.getInetAddress().getHostName() );
	}
	return remoteHostMB;
    }

    public String getLocalHost() {
	InetAddress localAddress = socket.getLocalAddress();
	localHost = localAddress.getHostName();
	return localHost;
    }

    public MessageBytes serverName(){
        if(! serverNameMB.isNull()) return serverNameMB;
        parseHostHeader();
        return serverNameMB;
    }

    public int getServerPort(){
        if(serverPort!=-1) return serverPort;
        parseHostHeader();
        return serverPort;
    }

    protected void parseHostHeader() {
	MessageBytes hH=getMimeHeaders().getValue("host");
        if (sslSupport != null){
            serverPort = 443;
        } else {
            serverPort = 80;
        }           
	if (hH != null) {
	    // XXX use MessageBytes
	    String hostHeader = hH.toString();
	    int i = hostHeader.indexOf(':');
	    if (i > -1) {
		serverNameMB.setString( hostHeader.substring(0,i));
                hostHeader = hostHeader.substring(i+1);
                try{
                    serverPort=Integer.parseInt(hostHeader);
                }catch(NumberFormatException  nfe){
                }
	    }else serverNameMB.setString( hostHeader);
            return;
	}
        serverPort = socket.getLocalPort();
	if( localHost != null ) {
	    serverNameMB.setString( localHost );
	}
	// default to localhost - and warn
	//	log("No server name, defaulting to localhost");
        serverNameMB.setString( getLocalHost() );
    }
 
    void setSSLSupport(SSLSupport s){
        sslSupport=s;
    }
 
}


class HttpResponse extends  Response {
    Http10 http;
    String reportedname;
    DateFormat dateFormat;
    
    public HttpResponse() {
        super();
    }

    public void init() {
	super.init();
	dateFormat=new SimpleDateFormat(DateTool.RFC1123_PATTERN,
					Locale.US);
	dateFormat.setTimeZone(DateTool.GMT_ZONE);
    }
    
    public void setSocket( Socket s ) {
	http=((HttpRequest)request).http;
    }

    public void recycle() {
	super.recycle();
    }

    public void setReported(String reported) {
        reportedname = reported;
    }

    public void endHeaders()  throws IOException {
	super.endHeaders();
	if(request.protocol().isNull() ||
	   request.protocol().equals("") ) // HTTP/0.9 
	    return;

	http.sendStatus( status, HttpMessages.getMessage( status ));

	// Check if a Date is to be added
	MessageBytes dateH=getMimeHeaders().getValue("Date");
	if( dateH == null ) {
	    // no date header set by user
	    MessageBytes dateHeader=getMimeHeaders().setValue(  "Date" );
	    dateHeader.setTime( System.currentTimeMillis(), dateFormat);
	}

	// return server name (or the reported one)
	if (reportedname == null) {
	    Context ctx = request.getContext();
	    String server = ctx != null ? ctx.getEngineHeader() : 
                ContextManager.TOMCAT_NAME + "/" + ContextManager.TOMCAT_VERSION;    
	    getMimeHeaders().setValue(  "Server" ).setString(server);
	} else {
	    if (reportedname.length() != 0)
		getMimeHeaders().setValue(  "Server" ).setString(reportedname);
	}
	
	http.sendHeaders( getMimeHeaders() );
    }

    public void doWrite( byte buffer[], int pos, int count)
	throws IOException
    {
	http.doWrite( buffer, pos, count);
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.