Package nginx.clojure.net

Source Code of nginx.clojure.net.NginxClojureAsynChannel

/**
*  Copyright (C) Zhang,Yuexiang (xfeep)
*
*/
package nginx.clojure.net;

import java.net.SocketException;
import java.nio.ByteBuffer;

import nginx.clojure.ChannelListener;
import nginx.clojure.NginxClojureRT;
import nginx.clojure.logger.TinyLogService;
import nginx.clojure.logger.TinyLogService.MsgType;

public class NginxClojureAsynChannel implements NginxClojureSocketHandler {
 
  protected ChannelListener<NginxClojureAsynChannel> listener;
  protected BufferChain connectFakeChain;
  protected BufferChain writeBusyChain;
  protected BufferChain freeChain;
  protected BufferChain readBusyChain;
  protected int pagesize = 1024 * 4;
  protected NginxClojureAsynSocket as;
  protected static TinyLogService log;
 
  public static class BufferChain {
    public ByteBuffer buffer;
    public BufferChain next;
    public CompletionListener listener;
    public Object attachement;
  }
 
  public static interface CompletionListener<T> {
    /**
     *
     * @param status
     *        read/written number of bytes , always > 0.
     * @param attachment
     *        the attachment object which was as the last read/write method input argument before CompletionListener argument.
     *        e.g. <code>write(buf, off, size, listener, attachement)</code>
     */
    public void onDone(long status, T attachment);
   
    /**
     *
     * @param code
     *        if code = 0, eof for read/write
     *        if code < 0, code is error code which range from
     *        NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR to NGX_HTTP_CLOJURE_SOCKET_ERR_OUTOFMEMORY
     *        We can get the error string by invoking buildError(code)
     * @param attachment
     *        the attachment object which was as the last read/write method input argument before CompletionListener argument.
     *        e.g. <code>write(buf, off, size, listener, attachement)</code>
     */
    public void onError(long code, T attachment);
  }
 
  public NginxClojureAsynChannel() {
    as = new NginxClojureAsynSocket(this);
    if (log == null) {
      log = new TinyLogService(TinyLogService.getSystemPropertyOrDefaultLevel(NginxClojureSocketImpl.NGINX_CLOJURE_LOG_SOCKET_LEVEL, MsgType.info), System.err, System.err);
    }
  }
 
  public NginxClojureAsynSocket getAsynSocket() {
    return as;
  }
 
  public String buildError(long sc) {
    if (sc == 0) {
      return "end of stream or connection reset!";
    }
    return as.buildError(sc);
  }
 
  protected <T> BufferChain fetchFreeChainAndCopyBuf(ByteBuffer buf, T attachement, CompletionListener<T> listener) {
    BufferChain result;
    BufferChain cur;
    int size = buf.remaining();
    result = cur = freeChain;
    while (size > 0) {
      if (freeChain != null) {
        cur.buffer.put(buf);
        cur.buffer.flip();
        freeChain = freeChain.next;
        size -= freeChain.buffer.remaining();
        if (freeChain != null) {
          cur = freeChain;
        }
      }else {
        ByteBuffer tmp = ByteBuffer.allocateDirect(pagesize);
        size -= pagesize;
        tmp.put(buf);
        tmp.flip();
        if (result == null) {
          result = cur = new BufferChain();
        }else {
          cur.next = new BufferChain();
          cur = cur.next;
        }
        cur.buffer = tmp;
      }
    }
    cur.attachement = attachement;
    cur.listener = listener;
    cur.next = null;
    return result;
  }
 
  protected void collectFreeChain() {
   
  }
 
  public void check()  {
    if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
      throw new IllegalAccessError("NginxClojureAsynChannel can only be operated in main thread");
    }
    if (as == null) {
      NginxClojureRT.UNSAFE.throwException(new SocketException("Socket not created!"));
    }
    if (as.isClosed()) {
      NginxClojureRT.UNSAFE.throwException(new SocketException("Socket Closed"));
    }
  }
 
  public Object getOption(int optID)  {
    check();
    return NginxClojureSocketImpl.getOption(as, optID);
  }
 
  /**
   * Set socket options, e.g. setOption(SO_TIMEOUT, 1000);
   * @param optID @see java.net.SocketOptions
   * @param v value of this option
   */
  public void setOption(int optID, Object v)  {
    check();
    NginxClojureSocketImpl.setOption(as, optID, v);
  }
 
  /**
   * if timeout is negative, it will be ignored. if timeout is 0, this means no timeout.
   */
  public void setConnectTimeout(long timeout) {
    check();
    as.setTimeout(timeout, -1, -1);
  }
 
  /**
   * if timeout is negative, it will be ignored. if timeout is 0, this means no timeout.
   */
  public void setReadTimeout(long timeout) {
    check();
    as.setTimeout(-1, timeout, -1);
  }
 
  /**
   * if timeout is negative, it will be ignored. if timeout is 0, this means no timeout.
   */
  public void setWriteTimeout(long timeout) {
    check();
    as.setTimeout(-1, -1, timeout);
  }
 
  /**
   * if timeout is negative, it will be ignored. if timeout is 0, this means no timeout.
   */
  public void setTimeout(long connectTimeout, long readTimeout, long writeTimeout) {
    as.setTimeout(connectTimeout, readTimeout, writeTimeout);
  }
 
 
  /**
   * connect to remote server
   * @param url
   *        e.g. "192.168.2.34:80" , "www.bing.com:80", or unix domain socket "unix:/var/mytest/server.sock"
   * @param listener
   *        completion listener
   * @param attachement
   *        when action is done or meets error this attachment will be passed to the method of the completion listener
   */
  public <T> void connect(String url, T attachement, CompletionListener<T> listener) {
    check();
    if (connectFakeChain == null && listener != null) {
      connectFakeChain = new BufferChain();
      connectFakeChain.attachement = attachement;
      connectFakeChain.listener = listener;
    }
    as.connect(url);
  }
 
  public <T> void write(byte[] buf, long off, long size, T attachement, CompletionListener<T> listener) {
    write(ByteBuffer.wrap(buf, (int)off, (int)size), attachement, listener);
  }
 
  public <T> void write(ByteBuffer buf, T attachement, CompletionListener<T> listener) {
    check();
    BufferChain chain = fetchFreeChainAndCopyBuf(buf, attachement, listener);
    if (writeBusyChain != null) {
      writeBusyChain.next = chain;
    }else {
      writeBusyChain = chain;
    }
   
    onWrite(as, 0);
  }
 
  public <T> void read(byte[] buf, long off, long size, T attachement, CompletionListener<T> listener) {
    read(ByteBuffer.wrap(buf, (int)off, (int)size), attachement, listener);
  }
 
    public <T> void read(ByteBuffer buf, T attachement, CompletionListener<T> listener) {
      check();
    BufferChain chain = new BufferChain();
    chain.attachement = attachement;
    chain.buffer = buf;
    chain.listener = listener;
    chain.next = null;
    if (readBusyChain != null) {
      readBusyChain.next = chain;
    }else {
      readBusyChain = chain;
    }
    onRead(as, 0);
  }
 
  public void setListener(ChannelListener<NginxClojureAsynChannel> listener) {
    this.listener = listener;
  }
 
  public ChannelListener<NginxClojureAsynChannel> getListener() {
    return listener;
  }

  @Override
  public void onConnect(NginxClojureAsynSocket s, long sc) {
    if (listener != null) {
      listener.onConnect(sc, this);
    }
    callOnEventNoThrows(connectFakeChain, sc, true);
    connectFakeChain = null;
  }
 
  protected void callOnEventNoThrows(BufferChain chain, long status, boolean onConnect) {
    if (chain.listener == null) {
      return;
    }
    try {
      if (status >= 0) {
        chain.listener.onDone(status, chain.attachement);
      }else {
        chain.listener.onError(status, chain.attachement);
      }
    }catch(Throwable e) {
      log.warn("unhandled errors", e);
    }
  }
 
  protected void onIO(NginxClojureAsynSocket s, long sc, boolean isRead) {

    if (log.isDebugEnabled()) {
      log.debug("asyn-channel#%d: on %s status=%d", as.s, (isRead ? "read" : "write") , sc);
    }
    BufferChain chain = isRead ? readBusyChain : writeBusyChain;
    if (sc != NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK) {
      while (chain != null) {
        callOnEventNoThrows(chain, sc, false);
        if (!isRead) {
          BufferChain head = freeChain;
          freeChain = chain;
          freeChain.next = head;
          freeChain.buffer.clear();
          freeChain.attachement = null;
          freeChain.listener = null;
        }
        chain = chain.next;
      }
      readBusyChain = writeBusyChain = null;
      return;
    }
   
    while (chain != null) {
      ByteBuffer buffer = chain.buffer;
      long rc = isRead ? s.read(buffer) : s.write(buffer);
      int c = buffer.position();
      if (log.isDebugEnabled()) {
        if (rc > 0) {
          log.debug("asyn-channel#%d: %s offset %d len %d return %d, total %d", as.s, (isRead ? "read" : "write"),
              buffer.position() - rc, buffer.limit(), rc, buffer.position());
        }
       
      }
      if (rc > 0) {
        if (!buffer.hasRemaining()) {
          if (isRead) {
            readBusyChain = chain.next;
          }else {
            writeBusyChain = chain.next;
          }
          callOnEventNoThrows(chain, c, false);
          chain = isRead ? readBusyChain : writeBusyChain;
        }
      } else if (rc <= 0) {
        if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
          return;
        }
        while (chain != null) {
          if (isRead) {
            readBusyChain = chain.next;
          }else {
            writeBusyChain = chain.next;
          }
          if (c > 0) {
            callOnEventNoThrows(chain, c, false);
            c = 0;
          }else {
            callOnEventNoThrows(chain, rc, false);
          }
         
          chain = isRead ? readBusyChain : writeBusyChain;
        }
        return;
      }
    }
 
  }

  @Override
  public void onRead(NginxClojureAsynSocket s, long sc) {
    onIO(s, sc, true);
  }

  @Override
  public void onWrite(NginxClojureAsynSocket s, long sc) {
    onIO(s, sc, false);
  }

  @Override
  public void onRelease(NginxClojureAsynSocket s, long sc) {
    if (log.isDebugEnabled()) {
      log.debug("asyn-channel#%d: on release status=%d", as.s, sc);
    }
    if (listener != null) {
      listener.onClose(this);
    }
  }
 
  public boolean isClosed() {
    return as == null ? true : as.isClosed();
  }
 
  public void close() {
    this.as.close();
  }
 
  public <T> T getContext() {
    return (T)as.getContext();
  }

  public <T> void setContext(T context) {
    as.setContext(context);
  }
}
TOP

Related Classes of nginx.clojure.net.NginxClojureAsynChannel

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.