/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.grizzly.websockets; import com.sun.grizzly.util.buf.ByteChunk; import com.sun.grizzly.util.net.URL; import java.io.EOFException; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; public class ClientNetworkHandler implements NetworkHandler { private SocketChannel channel; private URL url; private ClientWebSocketApplication app; private WebSocket webSocket; private ClientHandShake clientHS; private final ByteChunk chunk = new ByteChunk(); private boolean isHeaderParsed = false; ClientNetworkHandler(SocketChannel channel) { this.channel = channel; } public ClientNetworkHandler(URL url, ClientWebSocketApplication application) throws IOException { this.url = url; app = application; channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(new InetSocketAddress(url.getHost(), url.getPort())); channel.socket().setSoTimeout(30000); } SocketChannel getChannel() { return channel; } public void setChannel(SocketChannel channel) { this.channel = channel; } public void send(DataFrame frame) throws IOException { write(frame.frame()); } public SelectionKey getKey() { return channel.keyFor(app.getSelector()); } public void process(SelectionKey key) throws IOException { if (key.isValid()) { if (key.isConnectable()) { disableOp(SelectionKey.OP_CONNECT); doConnect(true); enableOp(SelectionKey.OP_READ); } else if (key.isReadable()) { unframe(); if (webSocket.isConnected()) { enableOp(SelectionKey.OP_READ); } } } } protected void doConnect(final boolean finishNioConnect) throws IOException { if (finishNioConnect) { channel.finishConnect(); } final boolean isSecure = "wss".equals(url.getProtocol()); final StringBuilder origin = new StringBuilder(); origin.append(isSecure ? "https://" : "http://"); origin.append(url.getHost()); if (!isSecure && url.getPort() != 80 || isSecure && url.getPort() != 443) { origin.append(":") .append(url.getPort()); } String path = url.getPath(); if ("".equals(path)) { path = "/"; } clientHS = new ClientHandShake(isSecure, origin.toString(), url.getHost(), String.valueOf(url.getPort()), path); write(clientHS.getBytes()); } protected void write(byte[] bytes) throws IOException { final ByteBuffer buffer = ByteBuffer.wrap(bytes); channel.write(buffer); } private void unframe() throws IOException { int lastRead; while ((lastRead = read()) > 0) { if (webSocket.isConnected()) { readFrame(); } else { byte[] serverKey = findServerKey(); if (serverKey == null) return; // not enough data try { clientHS.validateServerResponse(serverKey); } catch (HandshakeException e) { throw new IOException(e.getMessage()); } webSocket.onConnect(); } } if (lastRead == -1) { throw new EOFException(); } } private byte[] findServerKey() throws IOException { if (!isHeaderParsed) { while (true) { byte[] line = readLine(); if (line == null) return null; if (line.length == 0) break; } } isHeaderParsed = true; return readN(16); } private byte[] readLine() throws IOException { if (chunk.getLength() <= 0) return null; int idx = chunk.indexOf('\n', 0); if (idx != -1) { int eolBytes = 1; final int offset = chunk.getOffset(); idx += offset; if (idx > offset && chunk.getBuffer()[idx - 1] == '\r') { idx--; eolBytes = 2; } final int size = idx - offset; final byte[] result = new byte[size]; chunk.substract(result, 0, size); chunk.setOffset(chunk.getOffset() + eolBytes); // Skip \r\n or \n return result; } return null; } private byte[] readN(int n) throws IOException { if (chunk.getLength() < n) return null; final byte[] result = new byte[n]; chunk.substract(result, 0, n); return result; } private void enableOp(final int op) { final SelectionKey key = getKey(); final int ops = key.interestOps(); final int newOp = ops | op; if (newOp != ops) { key.interestOps(newOp); } } private void disableOp(final int op) { final SelectionKey key = getKey(); final int ops = key.interestOps(); final int newOp = ops & ~op; if (newOp != ops) { key.interestOps(newOp); } } public void shutdown() throws IOException { getKey().cancel(); channel.close(); app.remove(webSocket); } public WebSocket getWebSocket() { return webSocket; } public void setWebSocket(BaseWebSocket webSocket) { this.webSocket = webSocket; if (app != null) { app.register(this); } } protected void readFrame() throws IOException { while (read() > 0) { final DataFrame dataFrame = DataFrame.read(this); if (dataFrame != null) { dataFrame.respond(webSocket); } else { webSocket.close(); } } } /** * If necessary read more bytes from the channel. * * @return any number > -1 means bytes were read * * @throws IOException */ private int read() throws IOException { int count = chunk.getLength(); if (count < 1) { ByteBuffer bytes = ByteBuffer.allocate(WebSocketEngine.INITIAL_BUFFER_SIZE); while ((count = channel.read(bytes)) == WebSocketEngine.INITIAL_BUFFER_SIZE) { chunk.append(bytes.array(), 0, count); } if (count > 0) { chunk.append(bytes.array(), 0, count); } } final int length = chunk.getLength(); if (length <= 0) { return count; } return length; } public byte get() throws IOException { synchronized (chunk) { fill(); return (byte) chunk.substract(); } } private void fill() throws IOException { if (chunk.getLength() == 0) { read(); } } public boolean peek(byte... bytes) throws IOException { synchronized (chunk) { fill(); return chunk.startsWith(bytes); } } }