SingleHttpSessionIoHandler.java :  » Web-Server » asyncweb » org » safehaus » asyncweb » service » transport » mina » Java Open Source

Java Open Source » Web Server » asyncweb 
asyncweb » org » safehaus » asyncweb » service » transport » mina » SingleHttpSessionIoHandler.java
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
package org.safehaus.asyncweb.service.transport.mina;

import java.io.IOException;
import java.net.InetSocketAddress;

import org.apache.mina.common.DefaultWriteRequest;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoFilterAdapter;
import org.apache.mina.common.IoFutureListener;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.WriteFuture;
import org.apache.mina.common.WriteRequest;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.ProtocolDecoderException;
import org.apache.mina.handler.multiton.SingleSessionIoHandler;
import org.safehaus.asyncweb.codec.HttpServerCodecFactory;
import org.safehaus.asyncweb.codec.decoder.HttpDecoderException;
import org.safehaus.asyncweb.common.DefaultHttpRequest;
import org.safehaus.asyncweb.common.DefaultHttpResponse;
import org.safehaus.asyncweb.common.HttpRequest;
import org.safehaus.asyncweb.common.HttpResponseStatus;
import org.safehaus.asyncweb.common.HttpVersion;
import org.safehaus.asyncweb.common.MutableHttpResponse;
import org.safehaus.asyncweb.service.HttpServiceContext;
import org.safehaus.asyncweb.service.HttpServiceFilter;
import org.safehaus.asyncweb.service.ServiceContainer;
import org.safehaus.asyncweb.service.context.AbstractHttpServiceContext;
import org.safehaus.asyncweb.service.pipeline.RequestPipeline;
import org.safehaus.asyncweb.service.pipeline.RequestPipelineListener;
import org.safehaus.asyncweb.service.pipeline.StandardRequestPipeline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


class SingleHttpSessionIoHandler implements SingleSessionIoHandler {

  private static final Logger LOG = LoggerFactory.getLogger(SingleHttpSessionIoHandler.class);
  
  /**
   * The number of parsers we pre-allocate
   */
//  private static final int DEFAULT_PARSERS = 5;
  
  /**
   * The default idle time
   */
  private static final int DEFAULT_IDLE_TIME = 30000;
  
  /**
   * Out default pipeline
   */
  private static final int DEFAULT_PIPELINE = 100;
  
  private final ServiceContainer container;
  private final IoSession session;
  private final RequestPipeline pipeline;

  private HttpServiceContext currentContext;
  private int readIdleTime  = DEFAULT_IDLE_TIME;
  
  SingleHttpSessionIoHandler(ServiceContainer container, IoSession session) {
    this.container = container;
    this.session = session;
    this.pipeline = new StandardRequestPipeline(DEFAULT_PIPELINE);
    
    session.getConfig().setIdleTime(IdleStatus.READER_IDLE, readIdleTime);
    
    session.getFilterChain().addLast(
        "codec", new ProtocolCodecFilter(new HttpServerCodecFactory()));
    
    session.getFilterChain().addLast(
        "converter",
        new ContextConverter());
    
    session.getFilterChain().addLast(
        "pipeline",
        new RequestPipelineAdapter(pipeline));

    int i = 0;
    for (HttpServiceFilter serviceFilter: container.getServiceFilters()) {
      session.getFilterChain().addLast(
          "serviceFilter." + (i++),
          new ServiceFilterAdapter(serviceFilter));
    }
  }
  
  public void sessionCreated() {
  }

  public void sessionOpened() {
    LOG.info("Connection opened");
  }

  public void sessionClosed() {
    LOG.info("Connection closed");
  }

  /**
   * Invoked when this connection idles out.
   * If we are in the process of parsing a request, the current request
   * is rejected with a {@link HttpResponseStatus#REQUEST_TIMEOUT} response status.
   * 
   */
  public void sessionIdle(IdleStatus idleType) {
    if (session.getIdleCount(idleType) == 1) {
//      // FIXME currentRequest is always null now; we need to cooperate with a decoder.
//      if (currentContext != null) {
//        LOG.info("Read idled out while parsing request. Scheduling timeout response");
//        handleReadFailure(currentContext, HttpResponseStatus.REQUEST_TIMEOUT, "Timeout while reading request");
//      } else {
        LOG.info("Idled with no current request. Scheduling closure when pipeline empties");
        pipeline.runWhenEmpty(new Runnable() {
          public void run() {
            LOG.info("Pipeline empty after idle. Closing session");
            session.close();
          }
        });    
//      }
    }
  }

  public void exceptionCaught(Throwable cause) {
    MutableHttpResponse response = null;
    if (cause instanceof ProtocolDecoderException) {
      HttpResponseStatus status;
      if (cause instanceof HttpDecoderException) {
        status = ((HttpDecoderException) cause).getResponseStatus();
      } else {
        status = HttpResponseStatus.BAD_REQUEST;
      }
      
      LOG.warn("Bad request:", cause);
      
      response = new DefaultHttpResponse();
      response.setProtocolVersion(HttpVersion.HTTP_1_1);
      response.setStatus(status);
    } else if (cause instanceof IOException) {
      LOG.warn("IOException on HTTP connection", cause);
      session.close();
    } else {
      response = new DefaultHttpResponse();
      response.setProtocolVersion(HttpVersion.HTTP_1_1);
      response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
      LOG.warn("Unexpected exception from a service.", cause);
    }
    if (response != null) {
      HttpServiceContext context = this.currentContext;
      if (context == null) {
        context = createContext(new DefaultHttpRequest());
      }
      context.commitResponse(response);
    }
  }

  public void messageReceived(Object message) {
    // FIXME messageReceived invoked only when whole message is built.
    
    // When headers were built
    //sendContinuationIfRequested(request);
    
    // When body has been built
  }

  /**
   * Sends a continuation response for the specified request, if
   * the client has requested that a continuation response should
   * be provided.</br>
   * The continuation response is enqueued with our pipe-line.
   * Note that we do <i>not</i> commit the request - a final response
   * must still be provided.
   * 
   * TODO: We're not currently adding value here: If we cant route
   *       to a service, we'll still accept the request. We should
   *       add the ability to inject the request in to the container
   *       and let it decide whether the request can be handled.
   */
  private void sendContinuationIfRequested(HttpServiceContext context) {
    if (context.getRequest().requiresContinuationResponse()) {
      MutableHttpResponse continuationResponse = new DefaultHttpResponse();
      continuationResponse.setStatus(HttpResponseStatus.CONTINUE);
      context.commitResponse(continuationResponse);
      LOG.info("Scheduled continuation response");
    }
  }

  /**
   * Invoked when we fail to parse an incoming request.
   * We configure our parser to discard any further data received from the client,
   * and schedule a response with the appropriate failure code for the
   * current request
   * 
   * @param status  The status
   * @param message Failure message
   */
  private void handleReadFailure(HttpServiceContext context, HttpResponseStatus status, String message) {
    if (LOG.isInfoEnabled()) {
      LOG.info("Failed to handle client request. Reason: " + status);
    }
    MutableHttpResponse response = new DefaultHttpResponse();
    response.setStatusReasonPhrase(message);
    response.setStatus(status);
    context.commitResponse(response);
  }

  /**
   * Invoked when data wrote has been fully written.
   * If we have scheduled closure after sending a final response, we will
   * be provided with the <code>CLOSE_MARKER</code> as our marker object.<br/>
   * This signals us to schedule closure of the connection
   * 
   * @param message   The marker provided when writing data. If this is
   *                 our closure marker, we schedule closure of the connection
   */
  public void messageSent(Object message)  {
  }

  /**
   * Sets the read idle time for all connections
   * 
   * @param readIdleTime  The read idle time (seconds)
   */
  public void setReadIdleTime(int readIdleTime) {
    this.readIdleTime = readIdleTime;
  }
  
  private HttpServiceContext createContext(HttpRequest request) {
    return new DefaultHttpServiceContext(request);
  }

  private class ContextConverter extends IoFilterAdapter {

    @Override
    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
      nextFilter.filterWrite(
          session,
          new DefaultWriteRequest(
              ((HttpServiceContext) writeRequest.getMessage()).getCommittedResponse(),
              writeRequest.getFuture()));
    }

    @Override
    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
      HttpRequest request = (HttpRequest) message;
      HttpServiceContext context = createContext(request);
      currentContext = context;
      nextFilter.messageReceived(session, context);
    }
  }
  
  private class ServiceFilterAdapter extends IoFilterAdapter {
    private final HttpServiceFilter filter;

    public ServiceFilterAdapter(HttpServiceFilter filter) {
      this.filter = filter;
    }
    
    @Override
    public void messageReceived(final NextFilter nextFilter, final IoSession session, final Object message) throws Exception {
      org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter nextFilterAdapter =
        new org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter() {
          public void invoke() {
            nextFilter.messageReceived(session, message);
          }
      };
      filter.handleRequest(nextFilterAdapter, (HttpServiceContext) message);
    }
    
    @Override
    public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) throws Exception {
      org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter nextFilterAdapter =
        new org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter() {
          public void invoke() {
            nextFilter.filterWrite(session, writeRequest);
          }
      };
      
      HttpServiceContext context = (HttpServiceContext) writeRequest.getMessage();
      
      filter.handleResponse(nextFilterAdapter, context);
    }
  }
  
  private class RequestPipelineAdapter extends IoFilterAdapter {

    private final RequestPipeline pipeline;
    
    public RequestPipelineAdapter(final RequestPipeline pipeline) {
      this.pipeline = pipeline;
    }
    
    public void sessionOpened(final NextFilter nextFilter, final IoSession session) {
      pipeline.setPipelineListener(new RequestPipelineListener() {
        public void responseReleased(HttpServiceContext context) {
          nextFilter.filterWrite(
              session,
              new DefaultWriteRequest(
                  context, ((DefaultHttpServiceContext) context).getWriteFuture()));
        }
      });
      
      nextFilter.sessionOpened(session);
    }

    @Override
    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
      HttpServiceContext context = (HttpServiceContext) message;
      if (pipeline.addRequest(context)) {
        LOG.debug("Allocated slot in request pipeline");
        nextFilter.messageReceived(session, message);
      } else {
        // The client has filled their pipeline. Currently, this
        // triggers closure. Another option would be to drop read interest
        // until we drain. 
        LOG.warn("Could not allocate room in the pipeline for request");
        handleReadFailure(context, HttpResponseStatus.SERVICE_UNAVAILABLE, "Pipeline full");
      }
    }

    @Override
    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
      DefaultHttpServiceContext context = (DefaultHttpServiceContext) writeRequest.getMessage();
      context.setWriteFuture(writeRequest.getFuture());
      pipeline.releaseResponse(context);
      // nextFilter will be invoked when pipeline listener is notified.
    }
  }
  
  private class DefaultHttpServiceContext extends AbstractHttpServiceContext {
    private WriteFuture writeFuture;
    
    private DefaultHttpServiceContext(HttpRequest request) {
      super((InetSocketAddress) session.getRemoteAddress(), request, container);
    }

    private WriteFuture getWriteFuture() {
      return writeFuture;
    }

    private void setWriteFuture(WriteFuture writeFuture) {
      if (!isResponseCommitted()) {
        throw new IllegalStateException();
      }
      this.writeFuture = writeFuture;
    }

    @Override
    protected void doWrite(boolean requiresClosure) {
      currentContext = null;
      WriteFuture future = session.write(this);
      if (requiresClosure) {
        LOG.debug("Added CLOSE future listener.");
        future.addListener(IoFutureListener.CLOSE);
      }
    }
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.