PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/org.chromium.sdk.wipbackend.dev/src/org/chromium/sdk/internal/websocket/AbstractWsConnection.java

http://chromedevtools.googlecode.com/
Java | 316 lines | 247 code | 34 blank | 35 comment | 23 complexity | ff4ad5dbcee2304dccad445e0a750b0e MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. package org.chromium.sdk.internal.websocket;
  5. import java.io.IOException;
  6. import java.nio.ByteBuffer;
  7. import java.nio.CharBuffer;
  8. import java.nio.charset.Charset;
  9. import java.nio.charset.CharsetDecoder;
  10. import java.nio.charset.CharsetEncoder;
  11. import java.nio.charset.CoderResult;
  12. import java.util.concurrent.BlockingQueue;
  13. import java.util.concurrent.LinkedBlockingQueue;
  14. import java.util.logging.Level;
  15. import java.util.logging.Logger;
  16. import org.chromium.sdk.ConnectionLogger;
  17. import org.chromium.sdk.RelayOk;
  18. import org.chromium.sdk.SyncCallback;
  19. import org.chromium.sdk.internal.transport.AbstractSocketWrapper;
  20. import org.chromium.sdk.util.SignalRelay;
  21. import org.chromium.sdk.util.SignalRelay.AlreadySignalledException;
  22. import org.chromium.sdk.util.SignalRelay.SignalConverter;
  23. public abstract class AbstractWsConnection<INPUT, OUTPUT> implements WsConnection {
  24. protected static final Charset UTF_8_CHARSET = Charset.forName("UTF-8");
  25. private static final Logger LOGGER = Logger.getLogger(Hybi00WsConnection.class.getName());
  26. private final AbstractSocketWrapper<INPUT, OUTPUT> socketWrapper;
  27. private final ConnectionLogger connectionLogger;
  28. private volatile boolean isClosingGracefully = false;
  29. private final BlockingQueue<MessageDispatcher> dispatchQueue =
  30. new LinkedBlockingQueue<MessageDispatcher>();
  31. // Access must be synchronized on dispatchQueue.
  32. private boolean isDispatchQueueClosed = false;
  33. // Access must be synchronized on this.
  34. private boolean isOutputClosed = false;
  35. protected AbstractWsConnection(AbstractSocketWrapper<INPUT, OUTPUT> socketWrapper,
  36. ConnectionLogger connectionLogger) {
  37. this.socketWrapper = socketWrapper;
  38. this.connectionLogger = connectionLogger;
  39. try {
  40. linkedCloser.bind(socketWrapper.getShutdownRelay(), null, SOCKET_TO_CONNECTION);
  41. } catch (AlreadySignalledException e) {
  42. throw new IllegalStateException(e);
  43. }
  44. }
  45. public enum CloseReason {
  46. /** Socket has been shut down. */
  47. CONNECTION_CLOSED,
  48. /**
  49. * Some exception has terminated stream read thread.
  50. * Occasionally {@link #CONNECTION_CLOSED} may be replaced with this reason (we are not
  51. * accurate enough here).
  52. */
  53. INPUT_STREAM_PROBLEM,
  54. /**
  55. * Closed as requested by {@link WsConnection#close()}.
  56. */
  57. USER_REQUEST,
  58. /**
  59. * Connection close has been requested from remote side.
  60. */
  61. REMOTE_CLOSE_REQUEST,
  62. /**
  63. * Remote side silently closed connection (without breaking a message).
  64. */
  65. REMOTE_SILENTLY_CLOSED,
  66. }
  67. @Override
  68. public RelayOk runInDispatchThread(final Runnable runnable, final SyncCallback syncCallback) {
  69. MessageDispatcher messageDispatcher = new MessageDispatcher() {
  70. @Override
  71. boolean dispatch(Listener userListener) {
  72. RuntimeException ex = null;
  73. try {
  74. runnable.run();
  75. } catch (RuntimeException e) {
  76. ex = e;
  77. throw e;
  78. } finally {
  79. syncCallback.callbackDone(ex);
  80. }
  81. return false;
  82. }
  83. };
  84. synchronized (dispatchQueue) {
  85. if (isDispatchQueueClosed) {
  86. throw new IllegalStateException("Connection is closed");
  87. }
  88. dispatchQueue.add(messageDispatcher);
  89. }
  90. return DISPATCH_THREAD_PROMISES_TO_RELAY_OK;
  91. }
  92. @Override
  93. public void startListening(final Listener listener) {
  94. final INPUT loggableReader = socketWrapper.getLoggableInput();
  95. Runnable listenRunnable = new Runnable() {
  96. @Override
  97. public void run() {
  98. Exception closeCause = null;
  99. CloseReason closeReason = null;
  100. try {
  101. closeReason = runListenLoop(loggableReader);
  102. if (closeReason == CloseReason.REMOTE_SILENTLY_CLOSED) {
  103. LOGGER.log(Level.INFO,
  104. "Remote side silently closed connection without 'close' message");
  105. }
  106. } catch (IOException e) {
  107. closeCause = e;
  108. LOGGER.log(Level.SEVERE, "Connection read failure", e);
  109. } catch (InterruptedException e) {
  110. closeCause = e;
  111. closeReason = CloseReason.USER_REQUEST;
  112. LOGGER.log(Level.SEVERE, "Thread interruption", e);
  113. } finally {
  114. synchronized (dispatchQueue) {
  115. dispatchQueue.add(EOS_MESSAGE_DISPATCHER);
  116. isDispatchQueueClosed = true;
  117. }
  118. if (connectionLogger != null) {
  119. connectionLogger.handleEos();
  120. }
  121. if (closeReason == null) {
  122. closeReason = CloseReason.INPUT_STREAM_PROBLEM;
  123. }
  124. linkedCloser.sendSignal(closeReason, closeCause);
  125. }
  126. }
  127. };
  128. Thread readThread = new Thread(listenRunnable, "WebSocket listen thread");
  129. readThread.setDaemon(true);
  130. readThread.start();
  131. if (connectionLogger != null) {
  132. connectionLogger.start();
  133. }
  134. Runnable dispatchRunnable = new Runnable() {
  135. @Override
  136. public void run() {
  137. try {
  138. runImpl();
  139. } catch (InterruptedException e) {
  140. LOGGER.log(Level.SEVERE, "Thread interruption", e);
  141. }
  142. }
  143. private void runImpl() throws InterruptedException {
  144. while (true) {
  145. MessageDispatcher next = dispatchQueue.take();
  146. try {
  147. boolean isLast = next.dispatch(listener);
  148. if (isLast) {
  149. return;
  150. }
  151. } catch (RuntimeException e) {
  152. LOGGER.log(Level.SEVERE, "Exception in dispatch thread", e);
  153. }
  154. }
  155. }
  156. };
  157. Thread dispatchThread = new Thread(dispatchRunnable, "WebSocket dispatch thread");
  158. dispatchThread.setDaemon(true);
  159. dispatchThread.start();
  160. }
  161. @Override
  162. public abstract void sendTextualMessage(String message) throws IOException;
  163. protected abstract CloseReason runListenLoop(INPUT loggableReader)
  164. throws IOException, InterruptedException;
  165. public SignalRelay<?> getCloser() {
  166. return linkedCloser;
  167. }
  168. protected AbstractSocketWrapper<INPUT, OUTPUT> getSocketWrapper() {
  169. return socketWrapper;
  170. }
  171. protected boolean isClosingGracefully() {
  172. return isClosingGracefully;
  173. }
  174. /**
  175. * Caller must be synchronized on this.
  176. */
  177. protected boolean isOutputClosed() {
  178. return isOutputClosed;
  179. }
  180. /**
  181. * Caller must be synchronized on this.
  182. */
  183. protected void setOutputClosed(boolean isOutputClosed) {
  184. this.isOutputClosed = isOutputClosed;
  185. }
  186. protected BlockingQueue<MessageDispatcher> getDispatchQueue() {
  187. return dispatchQueue;
  188. }
  189. private final SignalRelay<CloseReason> linkedCloser =
  190. SignalRelay.create(new SignalRelay.Callback<CloseReason>() {
  191. @Override public void onSignal(CloseReason param, Exception cause) {
  192. isClosingGracefully = true;
  193. }
  194. });
  195. /**
  196. * A debug charset that simply encodes all non-ascii symbols as %DDD.
  197. * We need it for log console because web-socket connection is essentially a random
  198. * sequence of bytes.
  199. */
  200. protected static final Charset LOGGER_CHARSET =
  201. new Charset("Chromium_Logger_Charset", new String[0]) {
  202. @Override
  203. public boolean contains(Charset cs) {
  204. return this == cs;
  205. }
  206. @Override
  207. public CharsetDecoder newDecoder() {
  208. return new CharsetDecoder(this, 4 / 2, 4) {
  209. @Override
  210. protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
  211. while (in.hasRemaining()) {
  212. byte b = in.get();
  213. if (b < 20 && b != (byte) '\n') {
  214. if (out.remaining() < 4) {
  215. return CoderResult.OVERFLOW;
  216. }
  217. out.put('%');
  218. int code = b;
  219. int d1 = code / 100 % 10;
  220. int d2 = code / 10 % 10;
  221. int d3 = code % 10;
  222. out.put((char) ('0' + d1));
  223. out.put((char) ('0' + d2));
  224. out.put((char) ('0' + d3));
  225. } else {
  226. char ch = (char) b;
  227. if (ch == '%') {
  228. if (out.remaining() < 2) {
  229. return CoderResult.OVERFLOW;
  230. }
  231. out.put('%');
  232. out.put('%');
  233. } else {
  234. if (!out.hasRemaining()) {
  235. return CoderResult.OVERFLOW;
  236. }
  237. out.put(ch);
  238. }
  239. }
  240. }
  241. return CoderResult.UNDERFLOW;
  242. }
  243. };
  244. }
  245. @Override
  246. public CharsetEncoder newEncoder() {
  247. throw new UnsupportedOperationException();
  248. }
  249. };
  250. protected static void dumpByte(byte b, StringBuilder output) {
  251. output.append('%');
  252. int code = (b + 256) % 256;
  253. int d1 = code / 100 % 10;
  254. int d2 = code / 10 % 10;
  255. int d3 = code % 10;
  256. output.append((char) ('0' + d1));
  257. output.append((char) ('0' + d2));
  258. output.append((char) ('0' + d3));
  259. }
  260. static abstract class MessageDispatcher {
  261. /**
  262. * Dispatches message to user.
  263. * @return true if it was a last message in queue
  264. */
  265. abstract boolean dispatch(Listener userListener);
  266. }
  267. private static final MessageDispatcher EOS_MESSAGE_DISPATCHER = new MessageDispatcher() {
  268. @Override
  269. boolean dispatch(Listener userListener) {
  270. userListener.eofMessage();
  271. return true;
  272. }
  273. };
  274. private static final SignalConverter<AbstractSocketWrapper.ShutdownSignal, CloseReason>
  275. SOCKET_TO_CONNECTION =
  276. new SignalConverter<AbstractSocketWrapper.ShutdownSignal, CloseReason>() {
  277. @Override public CloseReason convert(AbstractSocketWrapper.ShutdownSignal source) {
  278. return CloseReason.CONNECTION_CLOSED;
  279. }
  280. };
  281. private static final RelayOk DISPATCH_THREAD_PROMISES_TO_RELAY_OK = new RelayOk() {};
  282. }