1 /* 2 * Copyright 2013 The Netty Project 3 * 4 * The Netty Project licenses this file to you under the Apache License, 5 * version 2.0 (the "License"); you may not use this file except in compliance 6 * with the License. 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package io.netty.handler.codec.http.websocketx; 17 18 import io.netty.channel.ChannelHandlerContext; 19 import io.netty.channel.ChannelInboundHandler; 20 import io.netty.channel.ChannelPipeline; 21 import io.netty.handler.codec.http.HttpHeaders; 22 23 import java.net.URI; 24 import java.util.List; 25 26 /** 27 * This handler does all the heavy lifting for you to run a websocket client. 28 * 29 * It takes care of websocket handshaking as well as processing of Ping, Pong frames. Text and Binary 30 * data frames are passed to the next handler in the pipeline (implemented by you) for processing. 31 * Also the close frame is passed to the next handler as you may want inspect it before close the connection if 32 * the {@code handleCloseFrames} is {@code false}, default is {@code true}. 33 * 34 * This implementation will establish the websocket connection once the connection to the remote server was complete. 35 * 36 * To know once a handshake was done you can intercept the 37 * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} and check if the event was of type 38 * {@link ClientHandshakeStateEvent#HANDSHAKE_ISSUED} or {@link ClientHandshakeStateEvent#HANDSHAKE_COMPLETE}. 39 */ 40 public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler { 41 42 private final WebSocketClientHandshaker handshaker; 43 private final boolean handleCloseFrames; 44 45 /** 46 * Returns the used handshaker 47 */ 48 public WebSocketClientHandshaker handshaker() { return handshaker; } 49 50 /** 51 * Events that are fired to notify about handshake status 52 */ 53 public enum ClientHandshakeStateEvent { 54 /** 55 * The Handshake was started but the server did not response yet to the request 56 */ 57 HANDSHAKE_ISSUED, 58 59 /** 60 * The Handshake was complete succesful and so the channel was upgraded to websockets 61 */ 62 HANDSHAKE_COMPLETE 63 } 64 65 /** 66 * Base constructor 67 * 68 * @param webSocketURL 69 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be 70 * sent to this URL. 71 * @param version 72 * Version of web socket specification to use to connect to the server 73 * @param subprotocol 74 * Sub protocol request sent to the server. 75 * @param customHeaders 76 * Map of custom headers to add to the client request 77 * @param maxFramePayloadLength 78 * Maximum length of a frame's payload 79 * @param handleCloseFrames 80 * {@code true} if close frames should not be forwarded and just close the channel 81 */ 82 public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol, 83 boolean allowExtensions, HttpHeaders customHeaders, 84 int maxFramePayloadLength, boolean handleCloseFrames) { 85 this(WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, version, subprotocol, 86 allowExtensions, customHeaders, maxFramePayloadLength), handleCloseFrames); 87 } 88 89 /** 90 * Base constructor 91 * 92 * @param webSocketURL 93 * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be 94 * sent to this URL. 95 * @param version 96 * Version of web socket specification to use to connect to the server 97 * @param subprotocol 98 * Sub protocol request sent to the server. 99 * @param customHeaders 100 * Map of custom headers to add to the client request 101 * @param maxFramePayloadLength 102 * Maximum length of a frame's payload 103 */ 104 public WebSocketClientProtocolHandler(URI webSocketURL, WebSocketVersion version, String subprotocol, 105 boolean allowExtensions, HttpHeaders customHeaders, 106 int maxFramePayloadLength) { 107 this(webSocketURL, version, subprotocol, 108 allowExtensions, customHeaders, maxFramePayloadLength, true); 109 } 110 111 /** 112 * Base constructor 113 * 114 * @param handshaker 115 * The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection 116 * was established to the remote peer. 117 * @param handleCloseFrames 118 * {@code true} if close frames should not be forwarded and just close the channel 119 */ 120 public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, boolean handleCloseFrames) { 121 this.handshaker = handshaker; 122 this.handleCloseFrames = handleCloseFrames; 123 } 124 125 /** 126 * Base constructor 127 * 128 * @param handshaker 129 * The {@link WebSocketClientHandshaker} which will be used to issue the handshake once the connection 130 * was established to the remote peer. 131 */ 132 public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker) { 133 this(handshaker, true); 134 } 135 136 @Override 137 protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception { 138 if (handleCloseFrames && frame instanceof CloseWebSocketFrame) { 139 ctx.close(); 140 return; 141 } 142 super.decode(ctx, frame, out); 143 } 144 145 @Override 146 public void handlerAdded(ChannelHandlerContext ctx) { 147 ChannelPipeline cp = ctx.pipeline(); 148 if (cp.get(WebSocketClientProtocolHandshakeHandler.class) == null) { 149 // Add the WebSocketClientProtocolHandshakeHandler before this one. 150 ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(), 151 new WebSocketClientProtocolHandshakeHandler(handshaker)); 152 } 153 } 154 }