Package org.xlightweb.server

Source Code of org.xlightweb.server.HttpServerConnection$ForwardingResponseHandler

/*
*  Copyright (c) xlightweb.org, 2008 - 2010. All rights reserved.
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb.server;

import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.ConnectException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xlightweb.AbstractHttpConnection;
import org.xlightweb.BadMessageException;
import org.xlightweb.BodyDataSink;
import org.xlightweb.BodyForwarder;
import org.xlightweb.HttpResponse;
import org.xlightweb.HttpResponseHeader;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IBodyDestroyListener;
import org.xlightweb.IHttpMessageHeader;
import org.xlightweb.IHttpSession;
import org.xlightweb.HttpUtils;
import org.xlightweb.IHttpConnectionHandler;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpMessage;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xlightweb.IHttpResponseHeader;
import org.xlightweb.NonBlockingBodyDataSource;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.MaxConnectionsExceededException;




/**
* Represents the server side endpoint implementation of a http connection. Typically, a
* HttpServerConnection will be created by the {@link HttpProtocolAdapter} which is used
* internally by the {@link HttpServer}.
*
* @author grro@xlightweb.org
*/
public final class HttpServerConnection extends AbstractHttpConnection {

    private static final Logger LOG = Logger.getLogger(HttpServerConnection.class.getName());
 
  private static final int CLOSE_DELAY_ON_OPEN_TRANSACTIONS_MILLIS = 1000;
   
  public static final boolean DEFAULT_REMOVE_REQUEST_CONNECTION_HEADER = false;
  public static final Integer DEFAULT_RECEIVE_TIMEOUT_MILLIS = Integer.MAX_VALUE;
  public static final boolean DEFAULT_AUTOHANDLE_CONNECTION_UPGRADE_HEADER = true;
  public static final boolean DEFAULT_SESSION_MANAGEMENT = true;

  static final int MIN_REQUEST_TIMEOUT_MILLIS = 1000;

 
  private static final DoNothingMessageHandler DO_NOTHING_HANDLER = new DoNothingMessageHandler();

 
  // close management
  private static boolean IS_CLOSE_CONNECTION_ON_5XX_RESPONSE = Boolean.parseBoolean(System.getProperty("org.xlightweb.server.closeConnectionOn5xxResponse", "true"));
  private boolean isCloseOnSendingError = false;
  private boolean isDelayedClosed = false;
  private final ConnectionCloser connectionCloser = new ConnectionCloser();

 
  // timeout support
  private static final long MIN_WATCHDOG_PERIOD_MILLIS = 10 * 1000;
  private TimeoutWatchDogTask watchDogTask;
  private Long requestTimeoutMillis;


  // flags
  boolean isAutohandleUpgadeHeader = DEFAULT_AUTOHANDLE_CONNECTION_UPGRADE_HEADER;

 
  // session support
  private static final boolean IS_SESSION_COOKIE_HTTPONLY = Boolean.parseBoolean(System.getProperty("org.xlightweb.server.sessionCookieHttpOnly", "true"));
  private final ISessionManager sessionManager;
  private final int sessionMaxInactiveIntervalSec;
  private boolean useCookies = true;
 
 
  // compress support
  private final int autocompressThresholdBytes;
 
  // max transaction support
  private Integer maxTransactions;
 
    
    // transaction monitor support
    private final TransactionMonitor transactionMonitor;
   

 
  // message handling
  private final IMessageHeaderHandler messageHandler = new MessageHeaderHandler();
 
 
  // handler support
  private final RequestHandlerAdapter requestHandlerAdapter;
 
 
  /**
   * constructor
   *
   * @param defaultEncncoding           the body default encoding
   * @param sessionManager              the session manager
   * @param transactionMonitor          the transaction monitor or <code>null</code>
   * @param tcpConnection               the tcp connection
   * @param requestHandler              the request handler
   * @param requestHandlerInfo          the request handle info
   * @param isCloseOnSendingError       true, if connection should be closed by sending an error
   * @param connectionHandlers          the connection handler
   * @param useCookies                  true, if cookies is used for session state management
   * @param autocompressThresholdBytes  the autocompress threshold
   * @param isAutoUncompress            true, if the request should be uncompressed
   * @throws IOException if an exception occurs
   */
  HttpServerConnection(String defaultEncoding, ISessionManager sessionManager, TransactionMonitor transactionMonitor, int defaultMaxInactiveIntervalSec, INonBlockingConnection tcpConnection, Object requestHandlerAdapter, boolean isCloseOnSendingError, List<IHttpConnectionHandler> connectionHandlers, boolean useCookies, int autocompressThresholdBytes, boolean isAutoUncompress) throws IOException {
    super(tcpConnection, false);
   
    this.sessionManager = sessionManager;
    this.transactionMonitor = transactionMonitor;
    this.sessionMaxInactiveIntervalSec = defaultMaxInactiveIntervalSec;
    this.isCloseOnSendingError = isCloseOnSendingError;
    this.useCookies = useCookies;
    this.autocompressThresholdBytes = autocompressThresholdBytes;
   
    setAutoUncompress(isAutoUncompress);

    this.requestHandlerAdapter = (RequestHandlerAdapter) requestHandlerAdapter;   
   
    if (connectionHandlers != null) {
      for (IHttpConnectionHandler connectionHandler : connectionHandlers) {
        addConnectionHandler(connectionHandler);
      }
    }
   
    setBodyDefaultEncoding(defaultEncoding);
   
    init();
   
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("[" + getId() + "] http server connection established");
    }   
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public boolean isServerSide() {
      return true;
  }
 

  /**
   * set the max transactions per connection. Setting this filed causes that
   * a keep-alive response header will be added
   *
   * @param maxTransactions  the max transactions
   */
  public void setMaxTransactions(int maxTransactions) {
    this.maxTransactions = maxTransactions;
  }
 

 
  /**
     * set the request body default encoding. According to RFC 2616 the
     * initial value is ISO-8859-1
     *  
     * @param encoding  the defaultEncoding
     */
    public void setRequestBodyDefaultEncoding(String defaultEncoding) {
        setBodyDefaultEncoding(defaultEncoding);
   
 
 
  /**
   * schedules the task
   *
   * @param task      the task to schedule
   * @param delay     the delay
   * @param period    the period
   */
  protected static void schedule(TimerTask task, long delay, long period) {
    AbstractHttpConnection.schedule(task, delay, period);
  }
 
 
  protected static int getSizeDataReceived(NonBlockingBodyDataSource body) {
      return AbstractHttpConnection.getSizeDataReceived(body);
  }
     
    protected static int getSizeWritten(BodyDataSink body) {
        return AbstractHttpConnection.getSizeWritten(body);
    }
        
 
    static Object wrap(IHttpRequestHandler requestHandler) {
    return new RequestHandlerAdapter(requestHandler);
  }

 
 
  /**
   * set the max receive timeout which is accepted for the connection. Setting this
   * field cases, that a keep-alive response header will be added
   *
   * @param requestTimout the timeout
   */
  public void setRequestTimeoutMillis(long requestTimeoutMillis) {
   
    if (requestTimeoutMillis < MIN_REQUEST_TIMEOUT_MILLIS) {
      LOG.warning("[" + getId() + "] try to set request timeout with " + requestTimeoutMillis + " millis. This value will be ignored because it is smaller that " + MIN_REQUEST_TIMEOUT_MILLIS + " millis");
    }

    if (requestTimeoutMillis <= 0) {
      requestHandlerAdapter.onRequestTimeout(this);
      return;
    }
   
    setLastTimeDataReceivedMillis(System.currentTimeMillis());
   
    if ((this.requestTimeoutMillis == null) || (this.requestTimeoutMillis != requestTimeoutMillis)) {
      this.requestTimeoutMillis = requestTimeoutMillis;
     
      if (requestTimeoutMillis == Long.MAX_VALUE) {
        terminateWatchDogTask();
       
      } else {
       
        long watchdogPeriod = 100;
       
        if (requestTimeoutMillis > 1000) {
          watchdogPeriod = requestTimeoutMillis / 10;
        }
       
        if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
          watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
        }
       
        updateWatchDog(watchdogPeriod);
      }
    }
  }




  private void checkRequestTimeout(long currentTimeMillis) {
       
      if (requestTimeoutMillis != null) {
          long timeoutReceived = getLastTimeDataReceivedMillis() + requestTimeoutMillis;
          long timeoutSend = getLastTimeWritten() + requestTimeoutMillis;
             
          if ((currentTimeMillis > timeoutReceived) && (currentTimeMillis > timeoutSend)) {
              onRequestTimeout();
          }
      }
  }
 
 
  private void onRequestTimeout() {
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("[" + getId() + "] request timeout " + DataConverter.toFormatedDuration(requestTimeoutMillis) + " reached");
    }
   
    terminateWatchDogTask();
    requestHandlerAdapter.onRequestTimeout(this);
  }
 
 
  private synchronized void updateWatchDog(long watchDogPeriod) {
      if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("[" + getId() + "] update timeout watchdog task period to " + DataConverter.toFormatedDuration(watchDogPeriod));
      }
    terminateWatchDogTask();

      watchDogTask = new TimeoutWatchDogTask(this);
      AbstractHttpConnection.schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
  }
 
 
  private synchronized void terminateWatchDogTask() {
    if (watchDogTask != null) {
          watchDogTask.close();
          watchDogTask = null;
      }
  }

 
  private static final class TimeoutWatchDogTask extends TimerTask implements Closeable {
   
    private WeakReference<HttpServerConnection> connectionRef = null;
   
    public TimeoutWatchDogTask(HttpServerConnection connection) {
      connectionRef = new WeakReference<HttpServerConnection>(connection);
    }
 
   
    @Override
    public void run() {
     
      WeakReference<HttpServerConnection> ref = connectionRef;
      if (ref != null) {
        HttpServerConnection connection = ref.get();
         
        if (connection == null)  {
          this.close();
           
        } else {
          connection.checkRequestTimeout(System.currentTimeMillis());
        }
      }
    }
   
    public void close() {
      this.cancel();
      connectionRef = null;
    }
  }
 

 

  /**
   * {@inheritDoc}
   */
  @Override
  protected void onProtocolException(Exception ex) {
    if (ex instanceof BadMessageException) {
      setPersistent(false);
      try {
        sendResponseMessage(new HttpResponse(((BadMessageException) ex).getStatus(), "text/html", generateErrorMessageHtml(400, ex.getMessage(), getId())), false);
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("[" + getId() + "] could not send error message " + 400 + " reason " + ioe.toString());
        }
        destroy();
      }
    } else {
      super.onProtocolException(ex);
    }
  }
 
 
  @Override
  protected void onMessageWritten() {
     
      if (!isPersistent()) {        
          if (getNumOpenTransactions() > 0) {
              if (LOG.isLoggable(Level.FINE)) {
                  LOG.fine("[" + getId() + "] connection is not persistent. closing it after " + DataConverter.toFormatedDuration(CLOSE_DELAY_ON_OPEN_TRANSACTIONS_MILLIS) + " -> openTransactions " + getNumOpenTransactions());
              }
             
              isDelayedClosed = true;
              destroy(CLOSE_DELAY_ON_OPEN_TRANSACTIONS_MILLIS);
             
          } else {
              if (LOG.isLoggable(Level.FINE)) {
                  LOG.fine("[" + getId() + "] connection is not persistent. closing it now");
              }
             
              destroy();
          }
      }
  }
 
  boolean isDelayedClosed() {
      return isDelayedClosed;
  }
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  protected IMessageHeaderHandler getMessageHeaderHandler() {
    return messageHandler;
  }

 
 
 
  @Override
  protected void onDisconnect() {
    super.onDisconnect();
   
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("[" + getId() + "] http server connection destroyed");
    }
  }

 

/* 
  private void suspendMessageReceiving() {
    try  {
      super.suspendReceiving();
    } catch (IOException ioe) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("[" + getId() + "] error occured by suspending read " + ioe.toString());
      }
    }
  }
 
  private void resumeMessageReceiving() {
    try  {
      super.resumeReceiving();
    } catch (IOException ioe) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("[" + getId() + "] error occured by resuming read " + ioe.toString());
      }
    }
  }
*/
 
 
  private void handleLifeCycleHeaders(IHttpRequest request) {

    IHttpRequestHeader header = request.getRequestHeader();
   
    String keepAliveHeader = header.getHeader("KeepAlive");
    if (keepAliveHeader != null) {
      String[] tokens = keepAliveHeader.split(",");
      for (String token : tokens) {
        handleKeepAlive(token);
      }
    }


    String connectionHeader = header.getHeader("Connection");
    if (connectionHeader != null) {
      String[] values = connectionHeader.split(",");

      for (String value : values) {
        value = value.trim();

        if (value.equalsIgnoreCase("close")) {
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("[" + getId() + "] received connection: closed header. destroying connection");
          }
          setPersistent(false);
        }
       
        if (value.equalsIgnoreCase("Keep-Alive")) {
          setPersistent(true);
        }
      }
    }
  }

 

  private void handleKeepAlive(String option) {
        Integer timeoutSec = null;
       
        if (option.toUpperCase().startsWith("TIMEOUT=")) {
            timeoutSec = Integer.parseInt(option.substring("TIMEOUT=".length(), option.length()));

        } else {
            timeoutSec = Integer.parseInt(option);
        }
       

        if ((timeoutSec != null) && (getBodyDataReceiveTimeoutMillis() == Long.MAX_VALUE)) {
            setBodyDataReceiveTimeoutMillis(timeoutSec * 1000L);
        }
  }
 
 

  private BodyDataSink sendResponseHeader(IHttpResponseHeader header, boolean autocompress) throws IOException {
   
    try{
      enhanceResponseHeader(header);
     
      BodyDataSink bodyDataSink;
     
     
      if (autocompress) {
        int length = header.getContentLength();
        if (length != -1) {
          header.removeHeader("Content-Length");
          header.setHeader("Transfer-Encoding", "chunked");
        }

        bodyDataSink = writeMessage(autocompressThresholdBytes, header);
       
        if (length != -1) {
          setAutocompressThreshold(bodyDataSink, 0);
        }
      } else {
        bodyDataSink = writeMessage(Integer.MAX_VALUE, header);
      }
     
      if (!isPersistent()) {
        setBodyCloseListener(bodyDataSink, connectionCloser);
      }
     
      return bodyDataSink;
     
    } catch (IOException ioe) {
      destroy();
      throw ioe;
    }
  }
 
 

  private BodyDataSink sendResponseHeader(IHttpResponseHeader header, int contentLength, boolean autocompress) throws IOException {

    try{
      enhanceResponseHeader(header);

      BodyDataSink bodyDataSink;
      if (autocompress && (contentLength > autocompressThresholdBytes)) {
        header.removeHeader("Content-Length");
        header.setHeader("Transfer-Encoding", "chunked");
       
        bodyDataSink = writeMessage(0, header);
      } else {
        bodyDataSink = writeMessage(header, contentLength)
      }
     
      if(!isPersistent()) {
        setBodyCloseListener(bodyDataSink, connectionCloser);
      }
   
     
      return bodyDataSink;
     
    } catch (IOException ioe) {
      destroy();
      throw ioe;
    }
  }


 
  private BodyDataSink sendResponseMessage(IHttpResponse response, boolean autocompress) throws IOException {

    try
      IHttpResponseHeader responseHeader = response.getResponseHeader();
     
      if (response.getStatus() == 100) {
                writeMessageSilence(response);
                return null;

      } else {
         
          enhanceResponseHeader(responseHeader);
         
          // does the message contain a body?
          if (response.hasBody()) {
                    if (response.getNonBlockingBody().getDataHandler() != null) {
                        LOG.warning("[" + getId() + "] a body handler is already assigned to the message body. current body handler will be removed");
                    }
       
                                      
                   
                   
                    if (autocompress) {
                      response.removeHeader("Content-Length");
                      response.setHeader("Transfer-Encoding", "chunked");
                     
                      if (LOG.isLoggable(Level.FINE)) {
                            LOG.fine("[" + getId() + "] sending: " + response.getResponseHeader());
                        }

                      return writeMessage(autocompressThresholdBytes, response);
                    } else {
                     
                      if (LOG.isLoggable(Level.FINE)) {
                            LOG.fine("[" + getId() + "] sending: " + response.getResponseHeader());
                        }
                      return writeMessage(Integer.MAX_VALUE, response);
                    }

                   
          // ... no its bodyless
          } else {
                   
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("[" + getId() + "] sending (bodyless): " + response.getResponseHeader());
                    }
             
                    if ((response.getContentLength() == -1) && (response.getStatus() != 100) && (response.getStatus() != 101)) {
                        response.setContentLength(0);
                    }
           
                    BodyDataSink bodyDataSink = writeMessage(responseHeader, 0);
                    bodyDataSink.setFlushmode(FlushMode.ASYNC);
                    bodyDataSink.close();
                       
                    if(!isPersistent()) {
                        closeQuitly();
                    }
                   
                    return null;
          }
      }
    } catch (IOException ioe) {
      destroy();
      throw ioe;
    }
  }
 

  private void enhanceResponseHeader(IHttpResponseHeader header) {

      if (header.getStatus() > 199) {
        String server = header.getServer();
        if (server == null) {
          header.setServer(ServerUtils.getComponentInfo());
        }
           
        if (header.getProtocol().equalsIgnoreCase("HTTP/1.0")) {
            setPersistent(false);
        }
       
        int remainingTransactions = 0;
        if (maxTransactions != null) {
          remainingTransactions = maxTransactions - getCountMessagesReceived();
          if (remainingTransactions <= 0) {
            setPersistent(false);
          }
        }
   
            if ((header.getStatus() >= 400) && (isCloseOnSendingError)) {
                setPersistent(false);
            }

       
        if ((isPersistent() && (header.getHeader("Connection") == null)) &&
          ((maxTransactions != null) || (requestTimeoutMillis != null))) {
         
          header.setHeader("Connection" , "Keep-Alive");
         
          String keepAliveValue = null;
          if (maxTransactions != null) {
            keepAliveValue = "max=" + remainingTransactions;
          }
         
          if (requestTimeoutMillis != null) {
            if (keepAliveValue == null) {
              keepAliveValue = "timeout=" + requestTimeoutMillis / 1000;
            } else {
              keepAliveValue += ", timeout=" + requestTimeoutMillis / 1000;
            }
          }
         
          header.setHeader("Keep-Alive", keepAliveValue);
   
        }
   
   
        String connectionHeader = header.getHeader("Connection");
        if ((connectionHeader != null) && connectionHeader.equalsIgnoreCase("close")) {
          setPersistent(false);
   
        } else {
          if (!isPersistent()) {
            header.setHeader("Connection", "close");
            header.removeHeader("Keep-Alive");
          }
         
        }
      }
  }
 
 
  private HttpSession getSession(IHttpRequest request) throws IOException {
    HttpSession session = null;
   
    if (useCookies) {
      List<String> cookieHeaders = request.getHeaderList("Cookie");
      for (String cookieHeader : cookieHeaders) {
        String[] cookies = cookieHeader.split(";");
        for (String cookie : cookies) {
          cookie = cookie.trim();
          int idx = cookie.indexOf("=");
          if (idx != -1) {
            String name = cookie.substring(0, idx);
            if (name.equals("JSESSIONID")) {
              String sessionId = cookie.substring(idx + 1, cookie.length()).trim();
              session = sessionManager.getSession(sessionId);
              if (session != null) {
                return session;
              }
            }
          }
        }
      }
     
    } else {
        String sessionToken = request.getMatrixParameter("jsessionid");
        if (sessionToken != null) {
            request.removeMatrixParameter("jsessionid");
            session = sessionManager.getSession(sessionToken);
            if (session != null) {
                return session;
        }
      }
    }
   
    return null;
  }

 

  private boolean isLargerOrEquals(String protocolVersion, String otherProtocolVersion) {
   
    try {
      int idx = protocolVersion.indexOf(".");
      int major = Integer.parseInt(protocolVersion.substring(0, idx));
      int minor = Integer.parseInt(protocolVersion.substring(idx + 1, protocolVersion.length()));
       
      int idxOther = otherProtocolVersion.indexOf(".");
      int majorOther = Integer.parseInt(otherProtocolVersion.substring(0, idxOther));
      int minorOther = Integer.parseInt(otherProtocolVersion.substring(idxOther + 1, otherProtocolVersion.length()));
       
      if (major > majorOther) {
        return true;
      } else if (major < majorOther) {
        return false;
      }
         
      if (minor > minorOther) {
        return true;
      } else if (minor < minorOther) {
        return false;
      }
     
      return true;
    } catch (Exception e) {
      throw new RuntimeException("[" + getId() + "] error occured by comparing protocol version " + protocolVersion + " and " + otherProtocolVersion);
    }
  }
 
 

  private boolean isContentTypeFormUrlencoded(IHttpMessage message) {
    if (!message.hasBody()) {
      return false;
    }
   
    String contentType = message.getContentType();
    if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencoded"))) {
      return true;
    }
   
    return false;
  }
 
 
 
  private final class ConnectionCloser implements Runnable {
   
    public void run() {
      closeQuitly();
    }
  }
 
 
 
  private final class MessageHeaderHandler implements IMessageHeaderHandler {

      public IHttpMessageHeader getAssociatedHeader() {
          return null;
      }

   
    public IMessageHandler onMessageHeaderReceived(IHttpMessage message) throws IOException {
      final IHttpRequest request = (IHttpRequest) message;
   
     
      if (transactionMonitor != null) {
          transactionMonitor.registerMessageHeaderReceived(HttpServerConnection.this, request.getRequestHeader());
      }
     
   
      // handle Connection header
      handleLifeCycleHeaders(request);
     
     
      if (LOG.isLoggable(Level.FINE)) {
       
        if (request.hasBody()) {
                    String body = "";
                   
                    String contentType = request.getContentType();
                    if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencode"))) {
                        body = request.getNonBlockingBody().toString() + "\n";                     
                    }
                   
                    LOG.fine("[" + getId() + "] request received  from " + getRemoteAddress() +
                             ":" + getRemotePort() +
                             " (" + getCountMessagesReceived() + ". request) " + request.getRequestHeader().toString() + body);
       
        } else {
                    LOG.fine("[" + getId() + "] bodyless request received from " + getRemoteAddress() +
                            ":" + getRemotePort() +
                            " (" + getCountMessagesReceived() + ". request) " + request.getRequestHeader().toString());
        }
      }
     
     
     
   
       
      // handle upgrade header
      if (isAutohandleUpgadeHeader) {
        String upgrade = request.getRequestHeader().getHeader("Upgrade");
        if ((upgrade != null) && upgrade.equalsIgnoreCase("TLS/1.0")) {

          if (getUnderlyingTcpConnection().isSecuredModeActivateable()) {
            suspendReceiving();
           
            HttpResponse response = new HttpResponse(101);
            response.setHeader("Connection", "Upgrade");
            response.setHeader("Upgrade", "TLS/1.0, HTTP/1.1");
            writeMessage(Integer.MAX_VALUE, response);
           
            getUnderlyingTcpConnection().activateSecuredMode();
         
            resumeReceiving();
           
          } else {
            sendResponseMessage(new HttpResponse(400, "text/html", generateErrorMessageHtml(400, "upgrade TLS is not supported", getId())), false);
          }
         
          return DO_NOTHING_HANDLER;
        }
      }
     
     
      boolean isFormUrlEncoded = isContentTypeFormUrlencoded(request);

      if (message.hasBody()) {

          // handle 100 continue header
              if (HttpUtils.isContainExpect100ContinueHeader(request.getRequestHeader())) {
                 
                  if (isFormUrlEncoded || requestHandlerAdapter.isInvokeOnMessageReceived()) {
                      if (LOG.isLoggable(Level.FINE)) {
                          if (requestHandlerAdapter.isInvokeOnMessageReceived()) {
                              LOG.fine("request handler (chain) will be invoked onMessageReceived -> autohandle 100-continue");
                          } else if (isFormUrlEncoded) {
                              LOG.fine("request contains FormUrlEncoded body -> autohandle 100-continue");
                          }
                      }
                     
                      request.setAttribute("org.xlightweb.100-continue-has-been-sent", "true");
                      writeMessageSilence(new HttpResponse(100));
                      return DO_NOTHING_HANDLER;
                  }
              }


         
         
          // will set body receive timeout, if exits
          if (getBodyDataReceiveTimeoutMillis() != Long.MAX_VALUE) {
              message.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(getBodyDataReceiveTimeoutMillis());
          }
         
          // is FORM encoded request?
              if (isFormUrlEncoded) {
                  ServerExchange exchange = new ServerExchange(HttpServerConnection.this, newFormEncodedRequestWrapper(request));
                  return new InvokeOnMessageReceivedMessageHandler(this, exchange);

              // is invokeOnMessage?
              } else if (requestHandlerAdapter.isInvokeOnMessageReceived()) {
                  ServerExchange exchange = new ServerExchange(HttpServerConnection.this, request);
                  return new InvokeOnMessageReceivedMessageHandler(this, exchange);
              }
      }

     
      return new IMessageHandler() {
       
          public void onHeaderProcessed() throws IOException {
            ServerExchange exchange = new ServerExchange(HttpServerConnection.this, request);
          exchange.perform();
          }
         
          public void onMessageReceived() throws IOException {
           if (transactionMonitor != null) {
             transactionMonitor.registerMessageReceived(HttpServerConnection.this, request);
           }
          }
         
          public void onBodyException(IOException ioe, ByteBuffer[] rawData) {
           if (transactionMonitor != null) {
             transactionMonitor.registerMessageReceivedException(HttpServerConnection.this, request, ioe);
           }
          }
        };
    }

   
   
        /**
         * {@inheritDoc}
         */     
    public void onHeaderException(IOException ioe, ByteBuffer[] rawData) {
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("[" + getId() + "] error occured by receiving request header " + ioe.toString());
        }
        destroy();
        }
       
   
  }

 

    private final class InvokeOnMessageReceivedMessageHandler implements IMessageHandler {
       
        private final MessageHeaderHandler headerHandler;
        private final ServerExchange exchange;
       
        public InvokeOnMessageReceivedMessageHandler(MessageHeaderHandler headerHandler, ServerExchange exchange) {
            this.headerHandler = headerHandler;
            this.exchange = exchange;
        }
       
        public void onHeaderProcessed() throws IOException {
         
        }
       
       
        /**
         * {@inheritDoc}
         */
        public void onMessageReceived() throws IOException {
            if (transactionMonitor != null) {
                transactionMonitor.registerMessageReceived(HttpServerConnection.this, exchange.getRequest());
            }
           
            exchange.perform();
        }
       
       
        /**
         * {@inheritDoc}
         */
        public void onBodyException(IOException ioe, ByteBuffer[] rawData) {
            if (transactionMonitor != null) {
                transactionMonitor.registerMessageReceivedException(HttpServerConnection.this, exchange.getRequest(), ioe);
            }
           
            headerHandler.onHeaderException(ioe, rawData);
        }
    }
   
 
 
 
  private class ServerExchange extends AbstractExchange {

    private final IHttpRequest request;
   
    private HttpSession session = null;
    private boolean isSessionCreated = false;

     

    protected ServerExchange(HttpServerConnection httpCon, IHttpRequest request) throws IOException {
        super(null, httpCon);
       
        this.request = request;
       
        if (HttpUtils.isContainExpect100ContinueHeader(request.getRequestHeader()) ||
          (request.getHeader("Connection") != null) && (request.getHeader("Connection").equalsIgnoreCase("Upgrade"))) {
          isCloseOnSendingError = true;
        }
           
      if (!sessionManager.isEmtpy()) {
        resolveSession();
      }
    }
   
   
    void perform() throws BadMessageException, IOException {
        requestHandlerAdapter.onRequest(this);
    }
   
   
    public IHttpSession getSession(boolean create) {

      resolveSession();
      if (session != null) {
        return session;
      }
     
      if (create) {
        isSessionCreated = true;
        try {
          int prefix = request.getContextPath().hashCode();
          session = sessionManager.getSession(sessionManager.newSession(Integer.toHexString(prefix)));
          session.setMaxInactiveInterval(sessionMaxInactiveIntervalSec);
          return session;
        } catch (IOException ioe) {
          throw new RuntimeException(ioe.toString());
        }
      }
     
      return null;
    }
   
   
    public String encodeURL(String url) {
      if (useCookies) {
        return url;
       
      } else {
       
        // add path parameter (for URL path paramter see http://doriantaylor.com/policy/http-url-path-parameter-syntax)
       
          int pos = url.indexOf('?');
          if (pos == -1) {
                pos = url.indexOf('#');
          }
         
          if (pos == -1) {
            return url + ";jsessionid=" + session.getId();
          } else {
                return url.substring(0, pos) + ";jsessionid=" + session.getId() + url.substring(pos);
          }
       }       
    }

   
   
    private void resolveSession() {
      if (session != null) {
        return;
      }
     
      try {
        session = HttpServerConnection.this.getSession(request);
        if (session != null) {
          String idPrefix = Integer.toHexString(request.getContextPath().hashCode());
          if (session.getId().startsWith(idPrefix)) {
            session.setLastAccessTime(System.currentTimeMillis());
          } else {
            session = null;
          }
        }
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("[" + getId() + "] error occured by resolving session " + ioe.toString());
        }
      }
    }
   
   
    public final IHttpRequest getRequest() {
      return request;
    }
 
   
   
    private boolean isResponseCompressable(IHttpResponseHeader header) {
      return (autocompressThresholdBytes != Integer.MAX_VALUE) &&
             HttpUtils.isAcceptEncdoingGzip(request.getRequestHeader()) &&
             header.getProtocolVersion().equals("1.1") &&
             ((header.getStatus() == 200) || (header.getStatus() == 201) || (header.getStatus() == 206)) &&
             HttpUtils.isCompressableMimeType(header.getContentType()) &&
             (header.getHeader("Content-Encoding") == null)
    }


    /**
     * {@inheritDoc}
     */
    public BodyDataSink send(IHttpResponseHeader header) throws IOException {
            ensureResponseHasNotBeenCommitted();
     
      handleCookieOnSend(header);

      // is chunked?
            if (header.getContentLength() == -1) {
               
                // is HTTP 0.9 or HTTP 1.0?
                if (header.getProtocolVersion().equalsIgnoreCase("0.9") || header.getProtocolVersion().equalsIgnoreCase("1.0")) {
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("[" + getId() + "] response is " + header.getProtocolVersion() + " version");
                    }

                    HttpResponseHeader newHeader;
                    if (header.getProtocolVersion().equalsIgnoreCase("0.9")) {
                        newHeader = new HttpResponseHeader(200);
                    } else {
                        newHeader = new HttpResponseHeader(header.getStatus());
                    }
                    newHeader.copyHeaderFrom(header);
                   
                    header = newHeader;
                }
               
   
          // does caller support chunked response? if not convert it to length-based response 
          if (!AbstractHttpConnection.isAcceptingChunkedResponseBody(getRequest())) {
            if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("[" + getId() + "] the requestor does not support chunked response messages (request protocol: " + getRequest().getProtocol() + "). Converting chunked response into simple response.");
            }
           
            HttpResponseHeader newHeader = new HttpResponseHeader(header.getStatus(), header.getReason());
            newHeader.copyHeaderFrom(header);
            newHeader.setProtocol(getRequest().getProtocol());
            newHeader.setHeader("Connection", "close");
           
            header = newHeader;
           
          } else {
              header.setTransferEncoding("chunked");
          }
            }
     
      setResponseCommited(true);
     
      final BodyDataSink bodyDataSink = HttpServerConnection.this.sendResponseHeader(header, isResponseCompressable(header));
     
      if (transactionMonitor != null) {
          transactionMonitor.registerMessageHeaderSent(request, header, bodyDataSink);
         
          Runnable task = new Runnable() {
              public void run() {
                  transactionMonitor.registerMessageSent(request);
              }
          };
          setBodyCloseListener(bodyDataSink, task);
      }
     
      return bodyDataSink;
    }
   


   
   
       

    /**
     * send the response in a plain body non-blocking mode
     *
     * @param header         the header
     * @param contentLength  the body content length
     * @return the body handle to write
     * @throws IOException if an exception occurs
     */
    public BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException {
      ensureResponseHasNotBeenCommitted();

      handleCookieOnSend(header);

      header.setContentLength(contentLength);

      try {
        // http protocol version downgrade necessary?
        if ((!getRequest().getProtocolVersion().equals(header.getProtocolVersion())) &&
          (isLargerOrEquals(header.getProtocolVersion(), getRequest().getRequestHeader().getProtocolVersion()))) {
         
          header.setHeader("Connection", "close");
          header.setProtocol(getRequest().getProtocol());
        }
       
      } catch (Exception e) {
        HttpResponse errorResponse = null;
        if (HttpUtils.isShowDetailedError()) {
          errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, DataConverter.toString(e), getId()));
         
        } else {
          errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, HttpUtils.getReason(400), getId()));
        }
        setResponseCommited(true);
        HttpServerConnection.this.sendResponseMessage(errorResponse, false);
        throw new IOException(e.toString());
      }


     
      setResponseCommited(true);
      final BodyDataSink bodyDataSink = HttpServerConnection.this.sendResponseHeader(header, contentLength, isResponseCompressable(header))
     
      if (transactionMonitor != null) {
          transactionMonitor.registerMessageHeaderSent(request, header, bodyDataSink);

          Runnable task = new Runnable() {
                    public void run() {
                        transactionMonitor.registerMessageSent(request);
                    }
                };
                setBodyCloseListener(bodyDataSink, task);
      }
     
      return bodyDataSink;
    }

   
    @Override
    protected void doSendContinue() throws IOException {
        writeMessageSilence(new HttpResponse(100));
    }
   

    /**
     * send the response
     *
     * @param response   the response
     * @throws IOException if an exception occurs
     */
    public void send(IHttpResponse response) throws IOException {
       
            if (response.hasBody() && isForwardable(response.getNonBlockingBody())) {
                BodyDataSink dataSink = send(response.getResponseHeader());
                forwardBody(response.getNonBlockingBody(), dataSink);
                return;
               
            } else {
          ensureResponseHasNotBeenCommitted();
         
          handleCookieOnSend(response.getResponseHeader());
   
          try {
            // request protocol version not equals response protocol version?
            if (!response.getProtocolVersion().equals(getRequest().getProtocolVersion())) {
             
              // simple (HTTP/0.9) response?
              if (response.getProtocolVersion().equals("0.9") && (response.getContentLength() == -1)) {
               
                HttpResponseHeader header = new HttpResponseHeader(200);
                header.copyHeaderFrom(response.getResponseHeader());
                header.setProtocol(getRequest().getProtocol());
                header.setHeader("Connection", "close");
               
                BodyDataSink bodyDataSink = HttpServerConnection.this.sendResponseHeader(header, false);
               
                NonBlockingBodyDataSource bodyDataSource = response.getNonBlockingBody();
                BodyForwarder forwarder = new BodyForwarder(bodyDataSource, bodyDataSink);
                bodyDataSource.setDataHandler(forwarder);
                return;
              }
             
              // http protocol version downgrade necessary?
              if (isLargerOrEquals(response.getResponseHeader().getProtocolVersion(), getRequest().getRequestHeader().getProtocolVersion())) {
                response.getResponseHeader().setProtocol(getRequest().getProtocol());
                response.getResponseHeader().setHeader("Connection", "close");
              }
            }
          } catch (Exception e) {
            HttpResponse errorResponse = null;
            if (HttpUtils.isShowDetailedError()) {
              errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, DataConverter.toString(e), getId()));
             
            } else {
              errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, HttpUtils.getReason(400), getId()));
            }
            setResponseCommited(true);
            HttpServerConnection.this.sendResponseMessage(errorResponse, false);
            throw new IOException(e.toString());
          }
   
          if (response.getStatus() != 100) {
              setResponseCommited(true);
          }
         
          BodyDataSink dataSink = HttpServerConnection.this.sendResponseMessage(response, isResponseCompressable(response.getResponseHeader()));
         
          if (transactionMonitor != null) {
              transactionMonitor.registerMessageHeaderSent(request, response.getResponseHeader(), dataSink);
             
              if (response.hasBody()) {
                 
                  BodyListener bodyListener = new BodyListener();
                  NonBlockingBodyDataSource ds = response.getNonBlockingBody();
                  ds.addCompleteListener(bodyListener);
                  ds.addDestroyListener(bodyListener);
                 
              } else {
                  transactionMonitor.registerMessageSent(request);
              }
          }
            }
    }
   
   
    private final class BodyListener implements IBodyCompleteListener, IBodyDestroyListener {
           
            @Execution(Execution.NONTHREADED)
            public void onComplete() throws IOException {
                transactionMonitor.registerMessageSent(request);
            }
           
            public void onDestroyed() throws IOException {
                transactionMonitor.registerMessageBodySentError(request);
            }
        }
   
   
    private void handleCookieOnSend(IHttpResponseHeader header) {
     
        if (header.getStatus() != 100) {
          if (session != null) {
            try {
              sessionManager.saveSession(session.getId());
            } catch (IOException ioe) {
              if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] error occured by saving session " + session.getId());
              }
            }
          }
         
          if (isSessionCreated && useCookies) {
              StringBuilder sb = new StringBuilder("JSESSIONID=" + session.getId() + ";path=/");
              if (IS_SESSION_COOKIE_HTTPONLY)  {
                  sb.append(";HttpOnly");
              }
           
            if (isSecure()) {
              sb.append(";secure");
            }
            header.addHeader("Set-Cookie", sb.toString());
          }
        }
    }
   

    public BodyDataSink forward(IHttpRequestHeader requestHeader) throws IOException, ConnectException, IllegalStateException {
      return forward(requestHeader, new ForwardingResponseHandler(this));
    }
   

    public BodyDataSink forward(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
   
      if (responseHandler == null) {
        responseHandler = new ForwardingResponseHandler(this);
      }

     
      BodyDataSink bodyDataSink = newEmtpyBodyDataSink(requestHeader);
       
      // send not handled error after the data sink is closed
      setBodyCloseListener(bodyDataSink, newCloseListener(responseHandler));  

       
      return bodyDataSink;
    }

   
    public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength) throws IOException, ConnectException, IllegalStateException {
      return forward(requestHeader, contentLength, new ForwardingResponseHandler(this));
    }
   

    public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
     
      if (responseHandler == null) {
        responseHandler = new ForwardingResponseHandler(this);
      }

     
      BodyDataSink bodyDataSink = newEmtpyBodyDataSink(requestHeader);
       
       
      // send not handled error after the data sink is closed
      setBodyCloseListener(bodyDataSink, newCloseListener(responseHandler));  
       
      return bodyDataSink;
    }

   
    private Runnable newCloseListener(final IHttpResponseHandler responseHandler) {
      return new Runnable() {
       
        public void run() {
          sendNotHandledError(responseHandler);
        }
      };
    }
 
   

    public void forward(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
      if (responseHandler == null) {
        responseHandler = new ForwardingResponseHandler(this);
      }
     
      sendNotHandledError(responseHandler);
    }
   
    public void forward(IHttpRequest request) throws IOException, ConnectException, IllegalStateException {
      forward(request, new ForwardingResponseHandler(this));
    }

     
    private void sendNotHandledError(final IHttpResponseHandler responseHandler) {
       
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("[" + getId() + "] no handler found (for requested resource). returning error message");
        }
       
        try {
            IHttpResponse response = new HttpResponse(404, "text/html", generateErrorMessageHtml(404, null, getId()));
            callResponseHandler(responseHandler, response);
        } catch (IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] could not send not handle response. " + ioe.toString());
            }
        }
    }
   
   
   
    /**
     * {@inheritDoc}
     */
    public void sendError(Exception e) {
      int code = 500;
     
      if (e instanceof BadMessageException) {
        code = ((BadMessageException) e).getStatus();
      } else if (e instanceof MaxConnectionsExceededException) {
                code = 503;
            }
     
      if (HttpUtils.isShowDetailedError()) {
        sendError(code, DataConverter.toString(e));
       
      } else {
        if (e instanceof BadMessageException) {
          sendError(code, e.getMessage());
        } else {
          sendError(code);
        }
      }
    }
   
   
   
 

    /**
     * send an error response
     *
     * @param errorCode   the error code
     * @param msg         the error message
     */
    public void sendError(int errorCode, String msg) {
      if (isCloseOnSendingError || ((errorCode >= 500) && IS_CLOSE_CONNECTION_ON_5XX_RESPONSE)) {
        setPersistent(false);
      }
     
      if (isResponseCommitted()) {
        throw new IllegalStateException("response is already committed");
      }

      super.sendError(errorCode, msg);
    }
  }
 
 
  @Execution(Execution.NONTHREADED)
  private static final class ForwardingResponseHandler implements IHttpResponseHandler {

    private IHttpExchange exchange = null;
   
    public ForwardingResponseHandler(IHttpExchange exchange) {
      this.exchange = exchange;
    }

   
    public void onResponse(IHttpResponse response) throws IOException {
      exchange.send(response);
    }

    public void onException(IOException ioe) throws IOException {
      exchange.sendError(ioe);
    }
  }
 
  private static final class DoNothingMessageHandler implements IMessageHandler {
    
    public void onHeaderProcessed() throws IOException { }
   
    public void onMessageReceived() throws IOException {  }
        
    public void onBodyException(IOException ioe, ByteBuffer[] rawData) {  }
  } 
}
TOP

Related Classes of org.xlightweb.server.HttpServerConnection$ForwardingResponseHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.