Package com.rubyeventmachine

Source Code of com.rubyeventmachine.EventableSocketChannel

/**
* $Id$
*
* Author:: Francis Cianfrocca (gmail: blackhedd)
* Homepage::  http://rubyeventmachine.com
* Date:: 15 Jul 2007
*
* See EventMachine and EventMachine::Connection for documentation and
* usage examples.
*
*
*----------------------------------------------------------------------------
*
* Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
* Gmail: blackhedd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of either: 1) the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version; or 2) Ruby's License.
*
* See the file COPYING for complete licensing information.
*
*---------------------------------------------------------------------------
*
*
*/

/**
*
*/
package com.rubyeventmachine;

/**
* @author francis
*
*/

import java.nio.channels.*;
import java.nio.*;
import java.util.*;
import java.io.*;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.*;

import java.security.*;

public class EventableSocketChannel implements EventableChannel {
 
  // TODO, must refactor this to permit channels that aren't sockets.
  SocketChannel channel;
  String binding;
  Selector selector;
  LinkedList<ByteBuffer> outboundQ;
  boolean bCloseScheduled;
 
  SSLEngine sslEngine;
 
 
  SSLContext sslContext;


  public EventableSocketChannel (SocketChannel sc, String _binding, Selector sel) throws ClosedChannelException {
    channel = sc;
    binding = _binding;
    selector = sel;
    bCloseScheduled = false;
    outboundQ = new LinkedList<ByteBuffer>();
   
    sc.register(selector, SelectionKey.OP_READ, this);
  }
 
  public String getBinding() {
    return binding;
  }
 
  /**
   * Terminate with extreme prejudice. Don't assume there will be another pass through
   * the reactor core.
   */
  public void close() {
    try {
      channel.close();
    } catch (IOException e) {
    }
  }
 
  public void scheduleOutboundData (ByteBuffer bb) {
    try {
      if ((!bCloseScheduled) && (bb.remaining() > 0)) {
        if (sslEngine != null) {
          ByteBuffer b = ByteBuffer.allocate(32*1024); // TODO, preallocate this buffer.
          sslEngine.wrap(bb, b);
          b.flip();
          outboundQ.addLast(b);
        }
        else {
          outboundQ.addLast(bb);
        }
        channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
      }
    } catch (ClosedChannelException e) {
      throw new RuntimeException ("no outbound data");     
    } catch (SSLException e) {
      throw new RuntimeException ("no outbound data");
    }
  }
 
  public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) {
    throw new RuntimeException ("datagram sends not supported on this channel");
  }
 
  /**
   * Called by the reactor when we have selected readable.
   */
  public void readInboundData (ByteBuffer bb) {
    try {
      channel.read(bb);
    } catch (IOException e) {
      throw new RuntimeException ("i/o error");
    }
  }
  /**
   * Called by the reactor when we have selected writable.
   * Return false to indicate an error that should cause the connection to close.
   * We can get here with an empty outbound buffer if bCloseScheduled is true.
   * TODO, VERY IMPORTANT: we're here because we selected writable, but it's always
   * possible to become unwritable between the poll and when we get here. The way
   * this code is written, we're depending on a nonblocking write NOT TO CONSUME
   * the whole outbound buffer in this case, rather than firing an exception.
   * We should somehow verify that this is indeed Java's defined behavior.
   * Also TODO, see if we can use gather I/O rather than one write at a time.
   * Ought to be a big performance enhancer.
   * @return
   */
  public boolean writeOutboundData(){
    while (!outboundQ.isEmpty()) {
      ByteBuffer b = outboundQ.getFirst();
      try {
        if (b.remaining() > 0)
          channel.write(b);
      }
      catch (IOException e) {
        return false;
      }

      // Did we consume the whole outbound buffer? If yes,
      // pop it off and keep looping. If no, the outbound network
      // buffers are full, so break out of here.
      if (b.remaining() == 0)
        outboundQ.removeFirst();
      else
        break;
    }

    if (outboundQ.isEmpty()) {
      try {
        channel.register(selector, SelectionKey.OP_READ, this);
      } catch (ClosedChannelException e) {
      }
    }
   
    // ALWAYS drain the outbound queue before triggering a connection close.
    // If anyone wants to close immediately, they're responsible for clearing
    // the outbound queue.
    return (bCloseScheduled && outboundQ.isEmpty()) ? false : true;
   }
 
  public void setConnectPending() throws ClosedChannelException {
    channel.register(selector, SelectionKey.OP_CONNECT, this);
  }
 
  /**
   * Called by the reactor when we have selected connectable.
   * Return false to indicate an error that should cause the connection to close.
   * @throws ClosedChannelException
   */
  public boolean finishConnecting() throws ClosedChannelException {
    try {
      channel.finishConnect();
    }
    catch (IOException e) {
      return false;
    }
    channel.register(selector, SelectionKey.OP_READ, this);
    return true;
  }
 
  public void scheduleClose (boolean afterWriting) {
    if (!afterWriting)
      outboundQ.clear();
    try {
      channel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE, this);
    } catch (ClosedChannelException e) {
      throw new RuntimeException ("unable to schedule close"); // TODO, get rid of this.
    }
    bCloseScheduled = true;
  }
  public void startTls() {
    if (sslEngine == null) {
      try {
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, null, null); // TODO, fill in the parameters.
        sslEngine = sslContext.createSSLEngine(); // TODO, should use the parameterized version, to get Kerb stuff and session re-use.
        sslEngine.setUseClientMode(false);
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException ("unable to start TLS"); // TODO, get rid of this.       
      } catch (KeyManagementException e) {
        throw new RuntimeException ("unable to start TLS"); // TODO, get rid of this.       
      }
    }
    System.out.println ("Starting TLS");
  }
 
  public ByteBuffer dispatchInboundData (ByteBuffer bb) throws SSLException {
    if (sslEngine != null) {
      if (true) throw new RuntimeException ("TLS currently unimplemented");
      System.setProperty("javax.net.debug", "all");
      ByteBuffer w = ByteBuffer.allocate(32*1024); // TODO, WRONG, preallocate this buffer.
      SSLEngineResult res = sslEngine.unwrap(bb, w);
      if (res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
        Runnable r;
        while ((r = sslEngine.getDelegatedTask()) != null) {
          r.run();
        }
      }
      System.out.println (bb);
      w.flip();
      return w;
    }
    else
      return bb;
  }
}
TOP

Related Classes of com.rubyeventmachine.EventableSocketChannel

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.