Package nginx.clojure.net

Source Code of nginx.clojure.net.NginxClojureSocketImpl$SocketOutputStream

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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.PortUnreachableException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import nginx.clojure.Coroutine;
import nginx.clojure.Coroutine.State;
import nginx.clojure.NginxClojureRT;
import nginx.clojure.logger.LoggerService;
import nginx.clojure.logger.TinyLogService;
import nginx.clojure.logger.TinyLogService.MsgType;

public class NginxClojureSocketImpl extends SocketImpl implements NginxClojureSocketHandler {
 
  final static int YIELD_CONNECT = 1;
  final static int YIELD_READ = 2;
  final static int YIELD_WRITE = 3;
 
  final static long SOCKET_FIELD_OFFSET_OF_SOCKETIMPL;
 
  public static final String NGINX_CLOJURE_LOG_SOCKET_LEVEL = "nginx.clojure.logger.socket.level";
 
  static {
    Field socketField = null;
    try {
      socketField = SocketImpl.class.getDeclaredField("socket");
    } catch (Throwable e) {
    }
    if (socketField != null) {
      SOCKET_FIELD_OFFSET_OF_SOCKETIMPL = NginxClojureRT.UNSAFE.objectFieldOffset(socketField);
    }else {
      SOCKET_FIELD_OFFSET_OF_SOCKETIMPL = 0;
    }
  }
 
  protected static LoggerService log;
 
  protected NginxClojureAsynSocket as;
  protected Coroutine coroutine;
  protected int yieldFlag = 0;
  protected long status = NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK;
  protected SocketInputStream inputStream;
  protected SocketOutputStream outputStream;

  public NginxClojureSocketImpl() {
    if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
      throw new IllegalAccessError("coroutine based sockets can only be called in main thread");
    }
    if (log == null) {
      log = new TinyLogService(TinyLogService.getSystemPropertyOrDefaultLevel(NGINX_CLOJURE_LOG_SOCKET_LEVEL, MsgType.info), System.err, System.err);
    }
  }
 

  protected Socket fetchSocket()  {
    if (SOCKET_FIELD_OFFSET_OF_SOCKETIMPL != 0) {
      log.debug("we'll get socket field object from NginxClojureSocketImpl");
      return (Socket) NginxClojureRT.UNSAFE.getObject(this, SOCKET_FIELD_OFFSET_OF_SOCKETIMPL);
    }
    return null;
  }
 
  protected void attachCoroutine() {
    Coroutine ac = Coroutine.getActiveCoroutine();
    if (coroutine == null || coroutine.getState() == Coroutine.State.FINISHED) {
      coroutine = ac;
    }
  }
 
  public static void setOption(NginxClojureAsynSocket as, int optID, Object v)  {
    NginxClojureRT.getLog().debug("set socket options: %d, val: %s" , optID, v+"");
    switch (optID) {
    case SO_TIMEOUT:
      if (v == null || !(v instanceof Integer)) {
                throw new IllegalArgumentException("wrong argument for SO_TIMEOUT, it must be Integer! But it is " ((v == null) ? "null" : v.getClass()));
            }
            int tmp = ((Integer) v).intValue();
            if (tmp < 0) {
                throw new IllegalArgumentException("timeout < 0");
            }
            as.setReadTimeout(tmp);
            return;
    case SO_RCVBUF:
      if (as.isConnected()) {
        throw new IllegalArgumentException("SO_RCVBUF must set before connected");
      }
      if (v == null || !(v instanceof Integer)
          || ((Integer) v).intValue() < 0) {
        NginxClojureRT.UNSAFE.throwException(new SocketException(
            "wrong argument for SO_RCVBUF, it must be Integer! But it is "
                + ((v == null) ? "null" : v.getClass())));
      }
      as.setReceiveBufferSize(((Integer) v).intValue());
      return;
    case TCP_NODELAY:
      as.setTcpNoDelay(((Boolean)v).booleanValue() ? 1 : 0);
      return;
    case SO_KEEPALIVE:
      as.setSoKeepAlive(((Boolean)v).booleanValue() ? 1 : 0);
      return;
    case SO_REUSEADDR:
      break;
    case SO_LINGER:
    case IP_TOS:
    case SO_BINDADDR:
        case SO_SNDBUF:
        case SO_OOBINLINE:
          NginxClojureRT.getLog().warn("not supported socket options: %d, val: %s just ignored" , optID, v+"");
          break;
        default:
          NginxClojureRT.UNSAFE.throwException( new SocketException("unknown TCP option: " + optID) );
        }
  }
 
  @Override
  public void setOption(int optID, Object v) throws SocketException {
    //now java.net.socket has done safe close check so we can ignore it
    //checkCreatedAndNotClosed();
    setOption(as, optID, v);
  }

  public boolean isClosed() {
    return as == null || as.isClosed();
  }

  public void checkCreatedAndNotClosed() throws SocketException {
    if (as == null) {
      throw new SocketException("Socket not created!");
    }
    if (as.isClosed()) {
      throw new SocketException("Socket Closed");
    }
  }

  public static Object getOption(NginxClojureAsynSocket as, int optID) {
    switch (optID) {
    case SO_TIMEOUT:
           return Integer.valueOf((int)as.getReadTimeout());
    case SO_RCVBUF:
      return Integer.valueOf((int)as.getReceiveBufferSize());
    case TCP_NODELAY:
      return Boolean.valueOf(as.getTcpNoDelay() == 1);
    case SO_KEEPALIVE:
      return Boolean.valueOf(as.getSoKeepAlive() == 1);
    }
    return null;
  }
 
  @Override
  public Object getOption(int optID) throws SocketException {
    //now java.net.socket has done safe close check so we can ignore it
    //checkCreatedAndNotClosed();
    return getOption(as, optID);
  }

 
  @Override
  protected void create(boolean stream) throws IOException {
    if (!stream) {
      throw new UnsupportedOperationException("stream = false not supported!");
    }
    as = new NginxClojureAsynSocket(this);
    /*
     * Although the default SO_TIMEOUT in java socket is 0, when SO_TIMEOUT = 0 a blocked Java Socket will call socket API connect
     * which will use a default timeout from system configuration. So to keep the same behavior with java build-in
     * Socket implementation, we should give a similar connect timeout.
     * e.g. On Linux the connection retries is in file /proc/sys/net/ipv4/tcp_syn_retries, on most case the value is 5
     * Linux tcp man page says it corresponds to approximately 180 seconds but my test result is about 127s
     */
    as.setConnectTimeout(120000);
  }

  @Override
  protected void connect(String host, int port) throws IOException {
    //now java.net.socket has done safe close check so we can ignore it
    //checkCreatedAndNotClosed();
    if (log.isDebugEnabled()) {
      log.debug("socket#%d: connecting to %s:%d", as.s , host, port);
    }
    as.connect(new StringBuilder(host).append(':').append(port).toString());
    if (!as.isConnected()) {
      yieldFlag = YIELD_CONNECT;
      if (log.isTraceEnabled()) {
        log.trace("show connect stack trace for debug", new Exception("DEBUG USAGE"));
      }
      if (status == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_RESOLVE) {
        throw new NoRouteToHostException(as.buildError(status));
      }else if (status == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_CONNECT) {
        throw new PortUnreachableException(as.buildError(status));
      }else if (status != NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK) {
        throw new ConnectException(as.buildError(status));
      }
      if (log.isDebugEnabled()) {
        log.debug("socket#%d: yield on connect", as.s);
      }
      attachCoroutine();
      Coroutine.yield();
      if (status != NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK) {
        throw new ConnectException(as.buildError(status));
      }
    }
  }

  @Override
  protected void connect(InetAddress address, int port) throws IOException {
    connect(address.getHostAddress(), port);
  }

  @Override
  protected void connect(SocketAddress address, int timeout) throws IOException {
    //now java.net.socket has done safe close check so we can ignore it
    //checkCreatedAndNotClosed();
    if (timeout > 0) { //see code comment in create method, we shouldn't overwrite the value.
      as.setConnectTimeout(timeout);
    }
    if (address == null || !(address instanceof InetSocketAddress))
      throw new IllegalArgumentException("unsupported address type");
    InetSocketAddress addr = (InetSocketAddress) address;
    if (addr.isUnresolved())
      throw new UnknownHostException(addr.getHostName());
    this.port = addr.getPort();
    this.address = addr.getAddress();
    connect(this.address, this.port);
  }

  @Override
  protected void bind(InetAddress host, int port) throws IOException {
    throw new UnsupportedOperationException("bind not supported!");
  }

  @Override
  protected void listen(int backlog) throws IOException {
    throw new UnsupportedOperationException("for client socket, listen not supported!");
  }

  @Override
  protected void accept(SocketImpl s) throws IOException {
    throw new UnsupportedOperationException("for client socket, accept not supported!");
  }

  @Override
  protected InputStream getInputStream() throws IOException {
    if (inputStream != null) {
      return inputStream;
    }
        //now java.net.socket has done safe close check so we can ignore it
//    if (as == null) {
//      throw new SocketException("Socket not created!");
//    }
//    as.checkConnected();
    return inputStream = new SocketInputStream(this);
  }

  @Override
  protected OutputStream getOutputStream() throws IOException {
    if (outputStream != null) {
      return outputStream;
    }
    //now java.net.socket has done safe close check so we can ignore it
//    if (as == null) {
//      throw new SocketException("Socket not created!");
//    }
//    as.checkConnected();
    return outputStream = new SocketOutputStream(this);
  }

  @Override
  protected int available() throws IOException {
    return as.available();
  }

  @Override
  protected void close() throws IOException {
//    checkCreatedAndNotClosed();
    if (!isClosed()) {
      if (inputStream != null) {
        inputStream.closed = true;
        inputStream = null;
      }
      if (outputStream != null) {
        outputStream.closed = true;
        outputStream = null;
      }
      if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
        if (log.isDebugEnabled()) {
          log.debug(
              String.format(
                  "socket#%d: close a coroutine based sockets not in main thread, so we post a event to main thread to do so",
                  as.s), new Exception(
                  "DEBUG USAGE--closeByPostEvent"));
        }
        NginxClojureRT.postCloseSocketEvent(this);
      }else {
        as.close();
        as = null;
      }
      yieldFlag = 0;
    }
  }
 
  public void closeByPostEvent() {
    if (as != null) {
      if (log.isDebugEnabled()) {
        log.debug("socket#%d: closed by post event", as.s);
      }
      as.close();
      as = null;
    }
  }

  @Override
  protected void shutdownInput() throws IOException {
    //make the same behavior with Java build-in socket implementation
    as.shutdown(NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_SHUTDOWN_READ);
  }
 
  @Override
  protected void shutdownOutput() throws IOException {
        //make the same behavior with Java build-in socket implementation
    as.shutdown(NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_SHUTDOWN_WRITE);
  }
 
  @Override
  protected void sendUrgentData(int data) throws IOException {
    throw new UnsupportedOperationException("sendUrgentData not supported!");
  }


  @Override
  public void onConnect(NginxClojureAsynSocket s, long sc) {
    if (log.isDebugEnabled()) {
      log.debug("socket#%d: on connect status=%d", as.s, sc);
    }
    status = sc;
    NginxClojureSocketImpl ns = (NginxClojureSocketImpl)s.getHandler();
    if (ns.yieldFlag == NginxClojureSocketImpl.YIELD_CONNECT){
      log.debug("socket#%d: find suspend on YIELD_CONNECT, we'ill resume it", as.s);
      ns.yieldFlag = 0;
      ns.coroutine.resume();
    }
  }


  @Override
  public void onRead(NginxClojureAsynSocket s, long sc) {
    if (log.isDebugEnabled()) {
      log.debug("socket#%d: on read status=%d", as.s, sc);
    }
    status = sc;
    NginxClojureSocketImpl ns = (NginxClojureSocketImpl)s.getHandler();
    if (ns.yieldFlag == NginxClojureSocketImpl.YIELD_READ){
      log.debug("socket#%d: find suspend on YIELD_READ, we'ill resume it", as.s);
      ns.yieldFlag = 0;
      ns.coroutine.resume();
    }   
  }


  @Override
  public void onWrite(NginxClojureAsynSocket s, long sc) {
    if (log.isDebugEnabled()) {
      log.debug("socket#%d: on write status=%d", as.s, sc);
    }
    status = sc;
    NginxClojureSocketImpl ns = (NginxClojureSocketImpl)s.getHandler();
    if (ns.yieldFlag == NginxClojureSocketImpl.YIELD_WRITE){
      log.debug("socket#%d: find suspend on YIELD_WRITE, we'ill resume it", as.s);
      ns.yieldFlag = 0;
      ns.coroutine.resume();
    }
  }


  @Override
  public void onRelease(NginxClojureAsynSocket s, long sc) {
    if (log.isDebugEnabled()) {
      log.debug("socket#%d: on release status=%d", as.s, sc);
    }
    status = sc;
    NginxClojureSocketImpl ns = (NginxClojureSocketImpl)s.getHandler();
    if (ns.coroutine.getState() == State.SUSPENDED){
      if (log.isDebugEnabled()) {
        log.warn("socket#%d: onRelease : coroutine is not finished, but we receive release event!", as.s);
        log.debug(String.format("socket#%d: onRelease : coroutine is not finished, but we receive release event!", as.s), new Exception("DEBUG USAGE--onRelease Warning"));
      }
    }
  }
 

  protected static class SocketInputStream extends InputStream {

    NginxClojureSocketImpl s;
    //in coroutine thread safe is not necessary, so we just make it work
    byte[] oba = new byte[1];
    boolean closed;
    boolean eof;
   
    public SocketInputStream(NginxClojureSocketImpl s) {
      this.s = s;
    }
   
    @Override
    public int available() throws IOException {
      checkClosed();
      return s.available();
    }
   
    @Override
    public int read() throws IOException {
      if ( read(oba, 0, 1) == 1) {
        return oba[0];
      }
      return -1;
    }
   
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
      checkClosed();
      if (len == 0) {
        return 0;
      }
      if (eof) {
        return -1;
      }
      if (off + len > b.length) {
        throw new IndexOutOfBoundsException("buffer space is too small, off + len > b.length");
      }
      if (log.isDebugEnabled()) {
        log.debug("socket#%d: enter read offset %d len %d", s.as.s, off, len);
      }
      long rc = 0;
      long c = 0;
      do {
        rc = s.as.read(b, off + c, len - c);
        if (log.isDebugEnabled()) {
          log.debug("socket#%d: read offset %d len %d return %d, total %d", s.as.s, off+c, len-c, rc, rc > 0 ? rc + c : c);
        }
       
        if (rc == 0) {
          eof = true;
          return c == 0 ? -1 : (int)c;
        }else if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
          if (c > 0) {
            return (int)c;
          }
          if (s.status == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_READ_TIMEOUT) {
            throw new SocketTimeoutException(s.as.buildError(s.status));
          }
          s.yieldFlag = YIELD_READ;
          if (log.isDebugEnabled()) {
            if (log.isTraceEnabled()) {
              log.trace(String.format("socket#%d: yield read", s.as.s), new Exception("DEBUG USAGE--yield read"));
            }else {
              log.debug(String.format("socket#%d: yield read", s.as.s));
            }
          }
          s.attachCoroutine();
          Coroutine.yield();
          if (s.status != NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK) {
            throw new SocketException(s.as.buildError(s.status));
          }
        }else if (rc < 0) {
          if (c > 0) {
            log.warn("socket#%d: meet error %d, but we have read some data (len=%d), just return it", s.as.s, rc, c);
            break;
          }
          throw new SocketException(s.as.buildError(rc));
        }else {
          c += rc;
        }
       
      }while( (rc > 0 || rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) && c < len);

      return (int)c;
    }
   
    void checkClosed() throws IOException {
      if (closed) {
        throw new IOException("SocketOutputStream closed already!");
      }
    }
   
    @Override
    public void close() throws IOException {
      if (log.isDebugEnabled()) {
        log.debug("socket#%d: close inputstream", s.as.s);
      }
//      checkClosed();
      if (closed) {
        return;
      }
      closed = true;
      Socket fs = s.fetchSocket();
      if (fs != null) {
        //although I think it's wrong  but it becomes de facto standard because of
        //Java build-in implementation. so we have no choice :-(.
        fs.close();
      }else {
        s.close();
      }
      s = null;
    }
  }
 
  protected static class SocketOutputStream extends OutputStream {
   
    NginxClojureSocketImpl s;
    byte[] oba = new byte[1];
    boolean closed;
   
    public SocketOutputStream(NginxClojureSocketImpl s) {
      this.s = s;
    }
   
    @Override
    public void write(int b) throws IOException {
      oba[0] = (byte)(b & 0xff);
      write(oba, 0, 1);
    }
   
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
      checkClosed();
      if (b == null) {
        throw new NullPointerException("byte[] can not be null");
      }
      if (off + len > b.length) {
        throw new IndexOutOfBoundsException("buffer space is too small, off + len > b.length");
      }
      if (log.isDebugEnabled()) {
        log.debug("socket#%d: enter write offset %d len %d", s.as.s, off, len);
      }
      long rc = 0;
      long c = 0;
      do {
        rc = s.as.write(b, off + c, len - c);
        if (log.isDebugEnabled()) {
          log.debug("socket#%d: write offset %d len %d return %d, total %d", s.as.s, off+c, len-c, rc, rc > 0 ? rc + c : c);
        }
        if (rc == 0) {
          return;
        }else if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) {
          if (s.status == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_WRITE_TIMEOUT) {
            throw new SocketTimeoutException(s.as.buildError(s.status));
          }
          s.yieldFlag = YIELD_WRITE;
          if (log.isDebugEnabled()) {
            if (log.isTraceEnabled()) {
              log.trace(String.format("socket#%d: yield write", s.as.s), new Exception("DEBUG USAGE--yield write"));
            }else {
              log.debug(String.format("socket#%d: yield write", s.as.s));
            }
           
          }
          s.attachCoroutine();
          Coroutine.yield();
          if (s.status != NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_OK) {
            throw new SocketException(s.as.buildError(s.status));
          }
        }else if (rc < 0) {
          throw new SocketException(s.as.buildError(rc));
        }else {
          c += rc;
        }
       
      }while((rc > 0 || rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) && c < len);
    }
   
    final void checkClosed() throws IOException {
      if (closed) {
        throw new IOException("SocketOutputStream closed already!");
      }
    }
   
    @Override
    public void close() throws IOException {
      if (log.isDebugEnabled()) {
        log.debug("socket#%d: close outputstream", s.as.s);
      }
//      checkClosed();
      if (closed) {
        return;
      }
     
      closed = true;
      Socket fs = s.fetchSocket();
      if (fs != null) {
        //although I think it's wrong  but it becomes de facto standard because of
        //Java build-in implementation. so we have no choice :-(.
        fs.close();
      }else {
        s.close();
      }
      s = null;
    }
  }
 
  public NginxClojureAsynSocket asynSocket() {
    return as;
  }
}
TOP

Related Classes of nginx.clojure.net.NginxClojureSocketImpl$SocketOutputStream

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.