1 /* 2 * Copyright (C) 2014 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp.ws; 17 18 import com.squareup.okhttp.Response; 19 import com.squareup.okhttp.internal.ws.WebSocketReader; 20 import java.io.IOException; 21 import java.util.concurrent.BlockingQueue; 22 import java.util.concurrent.LinkedBlockingQueue; 23 import java.util.concurrent.TimeUnit; 24 import okio.Buffer; 25 import okio.BufferedSource; 26 27 import static com.squareup.okhttp.ws.WebSocket.PayloadType.BINARY; 28 import static com.squareup.okhttp.ws.WebSocket.PayloadType.TEXT; 29 import static org.junit.Assert.assertEquals; 30 import static org.junit.Assert.assertNotNull; 31 import static org.junit.Assert.assertTrue; 32 33 public final class WebSocketRecorder implements WebSocketReader.FrameCallback, WebSocketListener { 34 public interface MessageDelegate { onMessage(BufferedSource payload, WebSocket.PayloadType type)35 void onMessage(BufferedSource payload, WebSocket.PayloadType type) throws IOException; 36 } 37 38 private final BlockingQueue<Object> events = new LinkedBlockingQueue<>(); 39 private MessageDelegate delegate; 40 41 /** Sets a delegate for the next call to {@link #onMessage}. Cleared after invoked. */ setNextMessageDelegate(MessageDelegate delegate)42 public void setNextMessageDelegate(MessageDelegate delegate) { 43 this.delegate = delegate; 44 } 45 onOpen(WebSocket webSocket, Response response)46 @Override public void onOpen(WebSocket webSocket, Response response) { 47 } 48 onMessage(BufferedSource source, WebSocket.PayloadType type)49 @Override public void onMessage(BufferedSource source, WebSocket.PayloadType type) 50 throws IOException { 51 if (delegate != null) { 52 delegate.onMessage(source, type); 53 delegate = null; 54 } else { 55 Message message = new Message(type); 56 source.readAll(message.buffer); 57 source.close(); 58 events.add(message); 59 } 60 } 61 onPing(Buffer buffer)62 @Override public void onPing(Buffer buffer) { 63 events.add(new Ping(buffer)); 64 } 65 onPong(Buffer buffer)66 @Override public void onPong(Buffer buffer) { 67 events.add(new Pong(buffer)); 68 } 69 onClose(int code, String reason)70 @Override public void onClose(int code, String reason) { 71 events.add(new Close(code, reason)); 72 } 73 onFailure(IOException e, Response response)74 @Override public void onFailure(IOException e, Response response) { 75 events.add(e); 76 } 77 nextEvent()78 private Object nextEvent() { 79 try { 80 Object event = events.poll(10, TimeUnit.SECONDS); 81 if (event == null) { 82 throw new AssertionError("Timed out."); 83 } 84 return event; 85 } catch (InterruptedException e) { 86 throw new AssertionError(e); 87 } 88 } 89 assertTextMessage(String payload)90 public void assertTextMessage(String payload) { 91 Message message = new Message(TEXT); 92 message.buffer.writeUtf8(payload); 93 assertEquals(message, nextEvent()); 94 } 95 assertBinaryMessage(byte[] payload)96 public void assertBinaryMessage(byte[] payload) { 97 Message message = new Message(BINARY); 98 message.buffer.write(payload); 99 assertEquals(message, nextEvent()); 100 } 101 assertPing(Buffer payload)102 public void assertPing(Buffer payload) { 103 assertEquals(new Ping(payload), nextEvent()); 104 } 105 assertPong(Buffer payload)106 public void assertPong(Buffer payload) { 107 assertEquals(new Pong(payload), nextEvent()); 108 } 109 assertClose(int code, String reason)110 public void assertClose(int code, String reason) { 111 assertEquals(new Close(code, reason), nextEvent()); 112 } 113 assertFailure(Class<? extends IOException> cls, String message)114 public void assertFailure(Class<? extends IOException> cls, String message) { 115 Object event = nextEvent(); 116 String errorMessage = 117 "Expected [" + cls.getName() + ": " + message + "] but was [" + event + "]."; 118 assertNotNull(errorMessage, event); 119 assertEquals(errorMessage, cls, event.getClass()); 120 assertEquals(errorMessage, cls.cast(event).getMessage(), message); 121 } 122 assertExhausted()123 public void assertExhausted() { 124 assertTrue("Remaining events: " + events, events.isEmpty()); 125 } 126 127 private static class Message { 128 public final WebSocket.PayloadType type; 129 public final Buffer buffer = new Buffer(); 130 Message(WebSocket.PayloadType type)131 private Message(WebSocket.PayloadType type) { 132 this.type = type; 133 } 134 toString()135 @Override public String toString() { 136 return "Message[" + type + " " + buffer + "]"; 137 } 138 hashCode()139 @Override public int hashCode() { 140 return type.hashCode() * 37 + buffer.hashCode(); 141 } 142 equals(Object obj)143 @Override public boolean equals(Object obj) { 144 if (obj instanceof Message) { 145 Message other = (Message) obj; 146 return type == other.type && buffer.equals(other.buffer); 147 } 148 return false; 149 } 150 } 151 152 private static class Ping { 153 public final Buffer buffer; 154 Ping(Buffer buffer)155 private Ping(Buffer buffer) { 156 this.buffer = buffer; 157 } 158 toString()159 @Override public String toString() { 160 return "Ping[" + buffer + "]"; 161 } 162 hashCode()163 @Override public int hashCode() { 164 return buffer.hashCode(); 165 } 166 equals(Object obj)167 @Override public boolean equals(Object obj) { 168 if (obj instanceof Ping) { 169 Ping other = (Ping) obj; 170 return buffer == null ? other.buffer == null : buffer.equals(other.buffer); 171 } 172 return false; 173 } 174 } 175 176 private static class Pong { 177 public final Buffer buffer; 178 Pong(Buffer buffer)179 private Pong(Buffer buffer) { 180 this.buffer = buffer; 181 } 182 toString()183 @Override public String toString() { 184 return "Pong[" + buffer + "]"; 185 } 186 hashCode()187 @Override public int hashCode() { 188 return buffer.hashCode(); 189 } 190 equals(Object obj)191 @Override public boolean equals(Object obj) { 192 if (obj instanceof Pong) { 193 Pong other = (Pong) obj; 194 return buffer == null ? other.buffer == null : buffer.equals(other.buffer); 195 } 196 return false; 197 } 198 } 199 200 private static class Close { 201 public final int code; 202 public final String reason; 203 Close(int code, String reason)204 private Close(int code, String reason) { 205 this.code = code; 206 this.reason = reason; 207 } 208 toString()209 @Override public String toString() { 210 return "Close[" + code + " " + reason + "]"; 211 } 212 hashCode()213 @Override public int hashCode() { 214 return code * 37 + reason.hashCode(); 215 } 216 equals(Object obj)217 @Override public boolean equals(Object obj) { 218 if (obj instanceof Close) { 219 Close other = (Close) obj; 220 return code == other.code && reason.equals(other.reason); 221 } 222 return false; 223 } 224 } 225 } 226