Package org.xlightweb.server

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

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. 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.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
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.RequestHandlerInfo;
import org.xlightweb.HttpResponse;
import org.xlightweb.ResponseHandlerInfo;
import org.xlightweb.HttpResponseHeader;
import org.xlightweb.IHttpSession;
import org.xlightweb.HttpUtils;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IBodyDataHandler;
import org.xlightweb.IHttpConnection;
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.IHttpRequestTimeoutHandler;
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;




/**
* 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());


  public static final String CALL_DEREGISTER_HANDLER_KEY = "org.xsocket.connection.http.server.call.deregisterHandler";
  public static final String CALL_DEREGISTER_HANDLER_DEFAULT = "false";
  private static boolean deregisterHandle;


  static {
    deregisterHandle = Boolean.parseBoolean(System.getProperty(CALL_DEREGISTER_HANDLER_KEY, CALL_DEREGISTER_HANDLER_DEFAULT));
  }


 
  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_AUTOCONFIRM_EXPECT_100CONTINUE_HEADER = true;
  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;
 
  // close management
  private final ConnectionCloser connectionCloser = new ConnectionCloser();
  private boolean isCloseOnSendingError = true;

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


  // flags
  boolean isAutconfirmExpect100ContinueHeader = DEFAULT_AUTOCONFIRM_EXPECT_100CONTINUE_HEADER;
  boolean isAutohandleUpgadeHeader = DEFAULT_AUTOHANDLE_CONNECTION_UPGRADE_HEADER;

 
  // session support
  private final ISessionManager sessionManager;
  private final int sessionMaxInactiveIntervalSec;
  private boolean useCookies = true;
 
 
  // max transaction support
  private Integer maxTransactions;
 
 
  // message handling
  private final ConcurrentLinkedQueue<IHttpRequest> requestQueue = new ConcurrentLinkedQueue<IHttpRequest>();
  private final IMessageHandler messageHandler = new MessageHandler();
 
 
  // handler support
  private final IHttpRequestHandler requestHandler;
  private final RequestHandlerInfo requestHandlerInfo;

 
 
 
 
  /**
   * constructor
   *
   * @param sessionManager           the session manager
   * @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
   * @throws IOException if an exception occurs
   */
  HttpServerConnection(ISessionManager sessionManager, int defaultMaxInactiveIntervalSec, INonBlockingConnection tcpConnection, IHttpRequestHandler requestHandler, RequestHandlerInfo requestHandlerInfo, boolean isCloseOnSendingError, List<IHttpConnectionHandler> connectionHandlers, boolean useCookies) throws IOException {
    super(tcpConnection, false);

    this.sessionManager = sessionManager;
    this.sessionMaxInactiveIntervalSec = defaultMaxInactiveIntervalSec;
    this.requestHandler = requestHandler;
    this.requestHandlerInfo = requestHandlerInfo;
    this.isCloseOnSendingError = isCloseOnSendingError;
    this.useCookies = useCookies;
   
    if (connectionHandlers != null) {
      for (IHttpConnectionHandler connectionHandler : connectionHandlers) {
        addConnectionHandler(connectionHandler);
      }
    }
   
    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;
  }
 
 
  /**
   * 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);
  }
 
 
  /**
   * returns the request handler info
   * @param requestHandler the request handler
   * @return the request handler info
   */
  protected static RequestHandlerInfo getRequestHandlerInfo(IHttpRequestHandler requestHandler) {
    return AbstractHttpConnection.getRequestHandlerInfo(requestHandler);
  }

  /**
   * returns the response handler info
   * @param responseHandler the response handler
   * @return the response handler info
   */
  protected static ResponseHandlerInfo getResponseHandlerInfo(IHttpResponseHandler responseHandler) {
    return AbstractHttpConnection.getResponseHandlerInfo(responseHandler);
  }
 
  /**
   * 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("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) {
      performRequestTimeoutHandler();
      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("request timeout " + DataConverter.toFormatedDuration(getBodyDataReceiveTimeoutMillis()) + " reached");
    }
   
    terminateWatchDogTask();
    performRequestTimeoutHandler();
  }
 
 
 
  private void performRequestTimeoutHandler() {

    if (requestHandlerInfo.isRequestTimeoutHandler()) {
     
      Runnable requestTimeoutHandlerCaller = new Runnable()  {
       
        public void run() {

          try {
            boolean isHandled = ((IHttpRequestTimeoutHandler) requestHandler).onRequestTimeout(HttpServerConnection.this);
            if (!isHandled) {
              if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("request timeout " + DataConverter.toFormatedDuration(requestTimeoutMillis) + " reached for http server connection. terminate connection (timeout handler returned false)");
              }

              closeSilence();
            }
           
          } catch (Exception e) {
            if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("[" + getId() + "] error occured by calling on request " + requestHandler + " " + e.toString());
            }
           
            destroy();
            throw new RuntimeException(e);
          }
        }   
      };
     
      // ... and perform the handler
      if (requestHandlerInfo.isRequestTimeoutHandlerMultithreaded()) {
        getExecutor().processMultithreaded(requestTimeoutHandlerCaller);
 
      } else {
        getExecutor().processNonthreaded(requestTimeoutHandlerCaller);
      }
     
    } else {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("request timeout  " + DataConverter.toFormatedDuration(requestTimeoutMillis) + " for http server connection reached. terminate connection");
      }

      closeSilence();
    }
  }
 

 
 
  private synchronized void updateWatchDog(long watchDogPeriod) {
      if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("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 {
        send(new HttpResponse(400, "text/html", generateErrorMessageHtml(400, ex.getMessage(), getId())));
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("could not send error message " + 400 + " reason " + ioe.toString());
        }
        destroy();
      }
    } else {
      super.onProtocolException(ex);
    }
  }
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  protected IMessageHandler getMessageHandler() {
    return messageHandler;
  }

 
 
 
  /**
   * {@inheritDoc}
   */
  @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.getKeepAlive();
    if (keepAliveHeader != null) {
      String[] tokens = keepAliveHeader.split(",");
      for (String token : tokens) {
        handleKeepAlive(token);
      }
    }


    String connectionHeader = header.getConnection();
    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("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);
        }
  }
 
 
  /**
   * processes a task multi threaded
   *
   * @param task the task
   */
  void processMultithreaded(Runnable task) {
    getExecutor().processMultithreaded(task);
  }
 
 
  /**
   * processes a task non threaded
   *
   * @param task the task
   */
  void processNonthreaded(Runnable task) {
    getExecutor().processNonthreaded(task);
  }
 
 
  private BodyDataSink send(IHttpResponseHeader header) throws IOException {
   
    try{
      if (deregisterHandle) {
        resumeMessageReceiving();
      }
     
      enhanceResponseHeader(header);
     
      BodyDataSink bodyDataSink = writeMessage(header, !isPersistent());
     
     
     
      if (!isPersistent()) {
        setBodyCloseListener(bodyDataSink, connectionCloser);
      }
     
      return bodyDataSink;
     
    } catch (IOException ioe) {
      destroy();
      throw ioe;
    }
  }
 
 

  private BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException {

    try{
      if (deregisterHandle) {
        resumeMessageReceiving();
      }
     
      enhanceResponseHeader(header);

      BodyDataSink bodyDataSink = writeMessage(header, !isPersistent(), contentLength);
     
      if(!isPersistent()) {
        setBodyCloseListener(bodyDataSink, connectionCloser);
      }
   
     
      return bodyDataSink;
     
    } catch (IOException ioe) {
      destroy();
      throw ioe;
    }
  }

  /**
   * send the response
   *
   * @param response   the response
   * @throws IOException if an exception occurs
   */
  public void send(IHttpResponse response) throws IOException {

    try{
      if (deregisterHandle) {
        resumeMessageReceiving();
      }
     
     
      IHttpResponseHeader responseHeader = response.getResponseHeader();
     
      enhanceResponseHeader(responseHeader);
     
      // body less request?
      if (response.getNonBlockingBody() == null) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("[" + getId() + "] sending (bodyless): " + response.getResponseHeader());
        }
 
        if (response.getContentLength() == -1) {
          response.setContentLength(0);
        }
 
        BodyDataSink bodyDataSink = writeMessage(responseHeader, !isPersistent(), 0);
        bodyDataSink.setFlushmode(FlushMode.ASYNC);
        bodyDataSink.close();
       
        if(!isPersistent()) {
          closeSilence();
        }
 
       
 
      // no, request has a body
      } else {
       
        if (response.getNonBlockingBody().getDataHandler() != null) {
          LOG.warning("a body handler is already assigned to the message body. current body handler will be removed");
        }
 
 
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("[" + getId() + "] sending: " + response.getResponseHeader());
        }
       
        writeMessage(response, !isPersistent());
      }
    } catch (IOException ioe) {
      destroy();
      throw ioe;
    }
  }
 

  private void enhanceResponseHeader(IHttpResponseHeader header) {

    String server = header.getServer();
    if (server == null) {
      header.setServer(ServerUtils.getComponentInfo());
    }

   
    int remainingTransactions = 0;
    if (maxTransactions != null) {
      remainingTransactions = maxTransactions - getCountMessagesReceived();
      if (remainingTransactions <= 0) {
        setPersistent(false);
      }
    }

   
    if ((isPersistent() && (header.getConnection() == null)) &&
      ((maxTransactions != null) || (requestTimeoutMillis != null))) {
     
      header.setConnection("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.getConnection();
    if ((connectionHeader != null) && connectionHeader.equalsIgnoreCase("close")) {
      setPersistent(false);

    } else {
      if (!isPersistent()) {
        header.setConnection("close");
      }
     
    }
  }
 
 
  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 uri = request.getRequestURI();
      int pos = uri.lastIndexOf(";");
      if (pos != -1) {
        String sessionToken = uri.substring(pos + 1, uri.length());
        if ((sessionToken != null) && (sessionToken.startsWith("jsessionid="))) {
          session = sessionManager.getSession(sessionToken.substring("jsessionid=".length(), sessionToken.length()));
          if (session != null) {
            return session;
          }
        }
      }
    }
   
    return null;
  }

 

  private boolean isLargerOrEquals(String protocolVersion, String otherProtocolVersion) {
   
    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;
  }
 
 

  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() {
      closeSilence();
    }
  }
 
 
 
  private final class MessageHandler implements IMessageHandler, Runnable {
   
    public boolean isBodylessMessageExpected() {
      return false;
    }

   
    public void onException(IOException ioe) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("error occured by receiving request " + ioe.toString());
      }
      destroy();
    }
   
   
   
    public void onMessage(IHttpMessage message) throws IOException {
      IHttpRequest request = (IHttpRequest) message;
     
   
      // handle Connection header
      handleLifeCycleHeaders(request);
     
     
      if (LOG.isLoggable(Level.FINE)) {
       
        if (request.getNonBlockingBody() == null) {
          LOG.fine("[" + getId() + "] bodyless request received from " + getRemoteAddress() +
               ":" + getRemotePort() +
               " (" + getCountMessagesReceived() + ". request) " + request.getRequestHeader().toString());
       
        } else {
          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);
        }
      }
     
     
     
   
       
      // handle upgrade header
      if (isAutohandleUpgadeHeader) {
        String upgrade = request.getRequestHeader().getUpgrade();
        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(response, false);
           
            getUnderlyingTcpConnection().activateSecuredMode();
         
            resumeReceiving();
           
          } else {
            send(new HttpResponse(400, "text/html", generateErrorMessageHtml(400, "upgrade TLS is not supported", getId())));
            return;
          }
         
          return;
        }
      }


      // handle 100 continue header
      if (isAutconfirmExpect100ContinueHeader) {
        String expectHeader = request.getHeader("Expect");
        if ((expectHeader != null) && expectHeader.equalsIgnoreCase("100-Continue")) {
          writeMessage(new HttpResponse(100), false);
        }
      }



      if (message.hasBody()) {
         
        if (getBodyDataReceiveTimeoutMillis() != Long.MAX_VALUE) {
          message.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(getBodyDataReceiveTimeoutMillis());
        }

                // is FORM encoded request?
                if (isContentTypeFormUrlencoded(request)) {
           
            final IHttpRequest req = request;
          IBodyCompleteListener cl = new IBodyCompleteListener() {
         
            @Execution(Execution.NONTHREADED)
            public void onComplete() throws IOException {
              handleMessage(newFormEncodedRequestWrapper(req));
            }
          };
          request.getNonBlockingBody().addCompleteListener(cl);
          return;
        }
      }
     
     
      handleMessage(request);
    }
   
   


    private void handleMessage(final IHttpRequest request) throws IOException {
         
      // handler deregistering?
      if (deregisterHandle) {
       
        IBodyCompleteListener deregisterHandlerListener = new IBodyCompleteListener() {
         
          @Execution(Execution.NONTHREADED)
          public void onComplete() throws IOException {
            HttpServerConnection.this.suspendMessageReceiving();
          }
        };
        request.getNonBlockingBody().addCompleteListener(deregisterHandlerListener);
      }
     
     
      // InvokeOn message received?
      if (requestHandlerInfo.isRequestHandlerInvokeOnMessageReceived() && request.hasBody()) {
       
        IBodyCompleteListener messageReceivedListener = new IBodyCompleteListener() {
         
          @Execution(Execution.NONTHREADED)
          public void onComplete() throws IOException {
            handle(request);
          }
        };

        request.getNonBlockingBody().addCompleteListener(messageReceivedListener);

       
      // ... no
      } else {
        handle(request);
      }
    }
   
   
    private void handle(IHttpRequest request) {

      if (requestHandler != null) {
       
        // add message to the queue
        requestQueue.add(request);

        // ... and perform the handler
        if (requestHandlerInfo.isRequestHandlerMultithreaded()) {
          getExecutor().processMultithreaded(this);
   
        } else {
          getExecutor().processNonthreaded(this);
        }
      }
    }
   

    public void run() {

      IHttpRequest request = null;
     
      do {
        request = requestQueue.poll();

        if (request != null) {
         
          ServerExchange exchange = new ServerExchange(request);
         
          // perform call back method
          try {
            if (requestHandlerInfo.isRequestHandlerSynchronizedOnSession()) {
              SessionSynchronizedRequestHandlerWrapper wrapper = new SessionSynchronizedRequestHandlerWrapper(requestHandler);
              wrapper.onRequest(exchange);
             
            } else {
              requestHandler.onRequest(exchange);
            }
           
          } catch (BadMessageException bme) {
            if (HttpUtils.isShowDetailedError()) {
              exchange.sendError(400, DataConverter.toString(bme));
            } else {
              exchange.sendError(400);
            }
           
          } catch (Exception e) {
            if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("[" +getId() + "] error occured by calling on request " + requestHandler + " " + e.toString());
            }
           
            if (!exchange.isResponseCommitted()) {
              if (HttpUtils.isShowDetailedError()) {
                exchange.sendError(500, DataConverter.toString(e));
              } else {
                exchange.sendError(500);
              }
            }

            closeSilence();
          }
        }

      } while (request != null);     
    }   
  }
 
 
 
 
  private class ServerExchange implements IHttpExchange {
   
    private final AtomicBoolean isResponseCommitted = new AtomicBoolean(false);
    private IHttpRequest request = null;
   
    private HttpSession session = null;
    private boolean isSessionCreated = false;
     
   
    protected ServerExchange(IHttpRequest request) {
      this.request = request;
     
      if (!sessionManager.isEmtpy()) {
        resolveSession();
      }
    }
   
   
    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("error occured by resolving session " + ioe.toString());
        }
      }
    }
   
   
   
    public final IHttpRequest getRequest() {
      return request;
    }
   
    public IHttpConnection getConnection() {
      return HttpServerConnection.this;
    }
   
   
    private final void ensureResponseHasNotBeenCommitted() throws IOException {
      if (isResponseCommitted.get() == true) {
        throw new IOException("response has already been committed");
      }
    }
   
 
    public final boolean isResponseCommitted() {
      return isResponseCommitted.get();
    }

    protected final void setResponseCommited(boolean isCommitted) {
      isResponseCommitted.set(isCommitted);
    }
   
   
    public final void destroy() {
      HttpServerConnection.this.destroy();
    }

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

     
      if (!AbstractHttpConnection.isAcceptingChunkedResponseBody(getRequest())) {
       
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("the requestor does not support chunked response messages (request protocol: " + getRequest().getProtocol() + "). Converting chunked response into length-defined response.");
        }
       
        HttpResponseHeader newHeader = new HttpResponseHeader(header.getStatus(), header.getReason());
        newHeader.copyHeaderFrom(header);
        newHeader.setProtocol(getRequest().getProtocol());
        newHeader.setHeader("Connection", "close");
       
        IBodyDataHandler bdh = new BodyHandlerDecorador(newHeader);
       
        return newBufferedBodyDataSink(bdh, header.getCharacterEncoding());
      }
     
     
      setResponseCommited(true);
     
      assert (header.getContentLength() == -1);
     
      header.setTransferEncoding("chunked");
      BodyDataSink bodyDataSink = HttpServerConnection.this.send(header);
     
      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);


      // 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());
      }

     
      setResponseCommited(true);
      BodyDataSink bodyDataSink = HttpServerConnection.this.send(header, contentLength)
     
      return bodyDataSink;
    }

   

    /**
     * send the response
     *
     * @param response   the response
     * @throws IOException if an exception occurs
     */
    public void send(IHttpResponse response) throws IOException {
      ensureResponseHasNotBeenCommitted();
     
      handleCookieOnSend(response.getResponseHeader());

     
      // 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");
         
          IBodyDataHandler bdh = new BodyHandlerDecorador(header);
          response.getNonBlockingBody().setDataHandler(bdh);
         
          return;
        }
       
        // http protocol version downgrade necessary?
        if (isLargerOrEquals(response.getResponseHeader().getProtocolVersion(), getRequest().getRequestHeader().getProtocolVersion())) {
          response.getResponseHeader().setProtocol(getRequest().getProtocol());
          response.getResponseHeader().setHeader("Connection", "close");
        }
       
      } 

     
      setResponseCommited(true);
      HttpServerConnection.this.send(response);
    }


    private final class BodyHandlerDecorador implements IBodyDataHandler {

      private final IHttpResponseHeader header;
     
      private boolean isHeaderWritten = false;
      private BodyDataSink bodyDataSink = null;
   
     
      public BodyHandlerDecorador(IHttpResponseHeader header) {
        this.header = header;
      }
       
       
      @Execution(Execution.NONTHREADED)
      public boolean onData(NonBlockingBodyDataSource bodyDataSource) {
         
        try {
          if (!isHeaderWritten) {
            isHeaderWritten = true;
            bodyDataSink = HttpServerConnection.this.writeMessage(header, !isPersistent());
            bodyDataSink.setFlushmode(FlushMode.ASYNC);
          }

           
          int available = bodyDataSource.available();
          if (available > 0) {
            ByteBuffer[] bufs = bodyDataSource.readByteBufferByLength(available);
            bodyDataSink.write(bufs);
          }
           
          if (available == -1) {
            bodyDataSink.close();
            bodyDataSource.setDataHandler(null);
          }
           
        } catch (IOException exception) {
          sendError(500);
        }
        return true;
      }
    }
   
   
   
    private void handleCookieOnSend(IHttpResponseHeader header) {
     
      if (session != null) {
        try {
          sessionManager.saveSession(session.getId());
        } catch (IOException ioe) {
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("error occured by saving session " + session.getId());
          }
        }
      }
     
      if (isSessionCreated && useCookies) {
        StringBuilder sb = new StringBuilder("JSESSIONID=" + session.getId() + ";path=/");
       
        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 DoNothingResponseHandler();
      }

     
      BodyDataSink bodyDataSink = newEmtpyBodyDataSink();
       
      // 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 DoNothingResponseHandler();
      }

     
      BodyDataSink bodyDataSink = newEmtpyBodyDataSink();
       
       
      // 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 DoNothingResponseHandler();
      }
     
      sendNotHandledError(responseHandler);
    }
   
    public void forward(IHttpRequest request) throws IOException, ConnectException, IllegalStateException {
      forward(request, new ForwardingResponseHandler(this));
    }

     
    private void sendNotHandledError(final IHttpResponseHandler responseHandler) {
       
      try {
        final IHttpResponse response = new HttpResponse(404, "text/html", generateErrorMessageHtml(404, null, getId()));
   
        Runnable task = new Runnable() {
           
          public void run() {
            try {
              responseHandler.onResponse(response);
            } catch (IOException ioe) {
              closeSilence();
            }
          }
           
        };
         
        if (HttpServerConnection.getResponseHandlerInfo(responseHandler).isResponseHandlerMultithreaded()) {
          getExecutor().processMultithreaded(task);
        } else {
          getExecutor().processNonthreaded(task)
        }
         
      } catch (IOException ioe) {
        throw new RuntimeException(ioe.toString());
      }
    }
   
   
   
    /**
     * {@inheritDoc}
     */
    public void sendError(Exception e) {
     
      int code = 500;
      if (e instanceof BadMessageException) {
        code = 400;
      }
      if (HttpUtils.isShowDetailedError()) {
        sendError(code, DataConverter.toString(e));
       
      } else {
        sendError(code);
      }
    }
   
   
    /**
     * send an error response
     *
     * @param errorCode   the error code
     */
    public void sendError(int errorCode) {
      sendError(errorCode, HttpUtils.getReason(errorCode));
    }


    /**
     * send an error response
     *
     * @param errorCode   the error code
     * @param msg         the error message
     */
    public void sendError(int errorCode, String msg) {
      if (isCloseOnSendingError) {
        setPersistent(false);
      }
     
      if (isResponseCommitted()) {
        throw new IllegalStateException("response is already committed");
      }
     
      try {
        send(new HttpResponse(errorCode, "text/html", generateErrorMessageHtml(errorCode, msg, getId())));
      } catch (IOException ioe) {
        if (LOG.isLoggable(Level.FINE)) {
          LOG.fine("could not send error message " + errorCode + " reason " + ioe.toString());
        }
        destroy();
      }
    }
  }
 
 
  @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);
    }
  }
 
 
  @Execution(Execution.NONTHREADED)
  private static final class DoNothingResponseHandler implements IHttpResponseHandler {

    public void onResponse(IHttpResponse response) throws IOException { }

    public void onException(IOException ioe) throws IOException { }
  }
 
 
 
  private static final class SessionSynchronizedRequestHandlerWrapper implements IHttpRequestHandler {
   
    private IHttpRequestHandler delegee = null;
   
    public SessionSynchronizedRequestHandlerWrapper(IHttpRequestHandler delegee) {
      this.delegee = delegee;
    }
   
   
    public void onRequest(IHttpExchange exchange) throws IOException {
     
      IHttpSession session = exchange.getSession(false);
     
      if (session == null) {
        delegee.onRequest(exchange);
       
      } else {
        synchronized (session) {
          delegee.onRequest(exchange);
        }
      }
    }
  }
}
TOP

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

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.