Home | History | Annotate | Download | only in elonen
      1 package fi.iki.elonen;
      2 
      3 import fi.iki.elonen.WebSocketFrame.CloseCode;
      4 import fi.iki.elonen.WebSocketFrame.CloseFrame;
      5 import fi.iki.elonen.WebSocketFrame.OpCode;
      6 
      7 import java.io.IOException;
      8 import java.io.InputStream;
      9 import java.io.OutputStream;
     10 import java.nio.charset.CharacterCodingException;
     11 import java.util.LinkedList;
     12 import java.util.List;
     13 
     14 public abstract class WebSocket {
     15     public static enum State {
     16         UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED
     17     }
     18 
     19     protected InputStream in;
     20 
     21     protected OutputStream out;
     22 
     23     protected WebSocketFrame.OpCode continuousOpCode = null;
     24 
     25     protected List<WebSocketFrame> continuousFrames = new LinkedList<WebSocketFrame>();
     26 
     27     protected State state = State.UNCONNECTED;
     28 
     29     protected final NanoHTTPD.IHTTPSession handshakeRequest;
     30 
     31     protected final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(
     32             NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null) {
     33         @Override
     34         protected void send(OutputStream out) {
     35             WebSocket.this.out = out;
     36             state = State.CONNECTING;
     37             super.send(out);
     38             state = State.OPEN;
     39             readWebsocket();
     40         }
     41     };
     42 
     43     public WebSocket(NanoHTTPD.IHTTPSession handshakeRequest) {
     44         this.handshakeRequest = handshakeRequest;
     45         this.in = handshakeRequest.getInputStream();
     46 
     47         handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_UPGRADE,
     48                 WebSocketResponseHandler.HEADER_UPGRADE_VALUE);
     49         handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_CONNECTION,
     50                 WebSocketResponseHandler.HEADER_CONNECTION_VALUE);
     51     }
     52 
     53     public NanoHTTPD.IHTTPSession getHandshakeRequest() {
     54         return handshakeRequest;
     55     }
     56 
     57     public NanoHTTPD.Response getHandshakeResponse() {
     58         return handshakeResponse;
     59     }
     60 
     61     // --------------------------------IO--------------------------------------
     62 
     63     protected void readWebsocket() {
     64         try {
     65             while (state == State.OPEN) {
     66                 handleWebsocketFrame(WebSocketFrame.read(in));
     67             }
     68         } catch (CharacterCodingException e) {
     69             onException(e);
     70             doClose(CloseCode.InvalidFramePayloadData, e.toString(), false);
     71         } catch (IOException e) {
     72             onException(e);
     73             if (e instanceof WebSocketException) {
     74                 doClose(((WebSocketException) e).getCode(), ((WebSocketException) e).getReason(), false);
     75             }
     76         } finally {
     77             doClose(CloseCode.InternalServerError, "Handler terminated without closing the connection.", false);
     78         }
     79     }
     80 
     81     protected void handleWebsocketFrame(WebSocketFrame frame) throws IOException {
     82         if (frame.getOpCode() == OpCode.Close) {
     83             handleCloseFrame(frame);
     84         } else if (frame.getOpCode() == OpCode.Ping) {
     85             sendFrame(new WebSocketFrame(OpCode.Pong, true, frame.getBinaryPayload()));
     86         } else if (frame.getOpCode() == OpCode.Pong) {
     87             onPong(frame);
     88         } else if (!frame.isFin() || frame.getOpCode() == OpCode.Continuation) {
     89             handleFrameFragment(frame);
     90         } else if (continuousOpCode != null) {
     91             throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence not completed.");
     92         } else if (frame.getOpCode() == OpCode.Text || frame.getOpCode() == OpCode.Binary) {
     93             onMessage(frame);
     94         } else {
     95             throw new WebSocketException(CloseCode.ProtocolError, "Non control or continuous frame expected.");
     96         }
     97     }
     98 
     99     protected void handleCloseFrame(WebSocketFrame frame) throws IOException {
    100         CloseCode code = CloseCode.NormalClosure;
    101         String reason = "";
    102         if (frame instanceof CloseFrame) {
    103             code = ((CloseFrame) frame).getCloseCode();
    104             reason = ((CloseFrame) frame).getCloseReason();
    105         }
    106         if (state == State.CLOSING) {
    107             //Answer for my requested close
    108             doClose(code, reason, false);
    109         } else {
    110             //Answer close request from other endpoint and close self
    111             State oldState = state;
    112             state = State.CLOSING;
    113             if (oldState == State.OPEN) {
    114                 sendFrame(new CloseFrame(code, reason));
    115             }
    116             doClose(code, reason, true);
    117         }
    118     }
    119 
    120     protected void handleFrameFragment(WebSocketFrame frame) throws IOException {
    121         if (frame.getOpCode() != OpCode.Continuation) {
    122             //First
    123             if (continuousOpCode != null) {
    124                 throw new WebSocketException(CloseCode.ProtocolError, "Previous continuous frame sequence not completed.");
    125             }
    126             continuousOpCode = frame.getOpCode();
    127             continuousFrames.clear();
    128             continuousFrames.add(frame);
    129         } else if (frame.isFin()) {
    130             //Last
    131             if (continuousOpCode == null) {
    132                 throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started.");
    133             }
    134             onMessage(new WebSocketFrame(continuousOpCode, continuousFrames));
    135             continuousOpCode = null;
    136             continuousFrames.clear();
    137         } else if (continuousOpCode == null) {
    138             //Unexpected
    139             throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started.");
    140         } else {
    141             //Intermediate
    142             continuousFrames.add(frame);
    143         }
    144     }
    145 
    146     public synchronized void sendFrame(WebSocketFrame frame) throws IOException {
    147         frame.write(out);
    148     }
    149 
    150     // --------------------------------Close-----------------------------------
    151 
    152     protected void doClose(CloseCode code, String reason, boolean initiatedByRemote) {
    153         if (state == State.CLOSED) {
    154             return;
    155         }
    156         if (in != null) {
    157             try {
    158                 in.close();
    159             } catch (IOException e) {
    160                 e.printStackTrace();
    161             }
    162         }
    163         if (out != null) {
    164             try {
    165                 out.close();
    166             } catch (IOException e) {
    167                 e.printStackTrace();
    168             }
    169         }
    170         state = State.CLOSED;
    171         onClose(code, reason, initiatedByRemote);
    172     }
    173 
    174     // --------------------------------Listener--------------------------------
    175 
    176     protected abstract void onPong(WebSocketFrame pongFrame);
    177 
    178     protected abstract void onMessage(WebSocketFrame messageFrame);
    179 
    180     protected abstract void onClose(CloseCode code, String reason, boolean initiatedByRemote);
    181 
    182     protected abstract void onException(IOException e);
    183 
    184     // --------------------------------Public Facade---------------------------
    185 
    186     public void ping(byte[] payload) throws IOException {
    187         sendFrame(new WebSocketFrame(OpCode.Ping, true, payload));
    188     }
    189 
    190     public void send(byte[] payload) throws IOException {
    191         sendFrame(new WebSocketFrame(OpCode.Binary, true, payload));
    192     }
    193 
    194     public void send(String payload) throws IOException {
    195         sendFrame(new WebSocketFrame(OpCode.Text, true, payload));
    196     }
    197 
    198     public void close(CloseCode code, String reason) throws IOException {
    199         State oldState = state;
    200         state = State.CLOSING;
    201         if (oldState == State.OPEN) {
    202             sendFrame(new CloseFrame(code, reason));
    203         } else {
    204             doClose(code, reason, false);
    205         }
    206     }
    207 }
    208