com.caucho.remote.websocket.WebSocketClient Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.remote.websocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import com.caucho.util.Base64;
import com.caucho.util.L10N;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.VfsStream;
import com.caucho.vfs.WriteStream;
import com.caucho.websocket.WebSocketContext;
import com.caucho.websocket.WebSocketEncoder;
import com.caucho.websocket.WebSocketListener;
/**
* WebSocketClient
*/
public class WebSocketClient implements WebSocketContext, WebSocketConstants {
private static final Logger log
= Logger.getLogger(WebSocketClient.class.getName());
private static final L10N L = new L10N(WebSocketClient.class);
private String _url;
private String _scheme;
private String _host;
private int _port;
private String _path;
private long _connectTimeout;
private String _virtualHost;
private boolean _isMasked;
private WebSocketListener _listener;
private Socket _s;
private ReadStream _is;
private WriteStream _os;
private boolean _isClosed;
private ClientContext _context;
private FrameInputStream _frameIs;
private WebSocketInputStream _wsIs;
private WebSocketOutputStream _wsOs;
private WebSocketWriter _wsWriter;
public WebSocketClient(String url,
WebSocketListener listener)
{
setUrl(url);
_listener = listener;
if (url == null)
throw new IllegalArgumentException();
if (_listener == null)
throw new IllegalArgumentException();
}
private void setUrl(String url)
{
_url = url;
parseUrl(url);
}
public void setVirtualHost(String virtualHost)
{
_virtualHost = virtualHost;
}
public void setConnectTimeout(long timeout)
{
_connectTimeout = timeout;
}
public void setMasked(boolean isMasked)
{
_isMasked = isMasked;
}
public void connect()
throws IOException
{
connect(null, null);
}
public void connect(String userName, String password)
throws IOException
{
if (_s != null)
return;
connectImpl(userName, password);
}
private void parseUrl(String url)
{
int p = url.indexOf("://");
if (p < 0)
throw new IllegalArgumentException(L.l("'{0}' is an illegal URL because it is missing a scheme",
url));
_scheme = url.substring(0, p);
int q = url.indexOf('/', p + 3);
String server;
if (q < 0) {
server = url.substring(p + 3);
_path = "/";
}
else {
server = url.substring(p + 3, q);
_path = url.substring(q);
}
p = server.indexOf(':');
if (p < 0) {
_host = server;
if ("https".equals(_scheme))
_port = 443;
else
_port = 80;
}
else {
_host = server.substring(0, p);
_port = Integer.parseInt(server.substring(p + 1));
}
}
protected void connectImpl(String userName, String password)
throws IOException
{
if (_listener == null)
throw new IllegalStateException("missing websocket listener");
int connectTimeout = (int) _connectTimeout;
_s = new Socket();
if (connectTimeout > 0)
_s.connect(new InetSocketAddress(_host, _port), connectTimeout);
else
_s.connect(new InetSocketAddress(_host, _port));
if ("https".equals(_scheme)) {
_s = openSsl(_s);
}
_is = VfsStream.openRead(_s.getInputStream());
_os = VfsStream.openWrite(_s.getOutputStream());
_os.print("GET " + _path + " HTTP/1.1\r\n");
if (_virtualHost != null)
_os.print("Host: " + _virtualHost + "\r\n");
else if (_host != null)
_os.print("Host: " + _host + "\r\n");
else
_os.print("Host: localhost\r\n");
byte []clientNonce = new byte[16];
String key = Base64.encode(clientNonce);
_os.print("Sec-WebSocket-Key: " + key + "\r\n");
String version = WebSocketConstants.VERSION;
_os.print("Sec-WebSocket-Version: " + version + "\r\n");
if (! _isMasked)
_os.print("Sec-WebSocket-Extensions: x-unmasked\r\n");
_os.print("Upgrade: WebSocket\r\n");
_os.print("Connection: Upgrade\r\n");
_os.print("\r\n");
_os.flush();
parseHeaders(_is);
_frameIs = new UnmaskedFrameInputStream();
_frameIs.init(this, _is);
// _wsOut = new WebSocketOutputStream(_out);
// _wsIn = new WebSocketInputStream(_is);
_os.flush();
_context = new ClientContext();
_listener.onStart(this);
// XXX: ThreadPool?
Thread thread = new Thread(_context);
thread.setDaemon(true);
thread.start();
}
private Socket openSsl(Socket s)
throws ConnectException
{
try {
SSLContext context = SSLContext.getInstance("TLS");
javax.net.ssl.TrustManager tm =
new javax.net.ssl.X509TrustManager() {
public java.security.cert.X509Certificate[]
getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] cert, String foo) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] cert, String foo) {
}
};
context.init(null, new javax.net.ssl.TrustManager[] { tm }, null);
SSLSocketFactory factory = context.getSocketFactory();
return factory.createSocket(s, _host, _port, true);
} catch (ConnectException e) {
throw new ConnectException("SSL " + _host + ":" + _port + ": " + e.getMessage());
} catch (Exception e) {
throw new ConnectException("SSL " + _host + ":" + _port + ": " + e.toString());
}
}
protected void parseHeaders(ReadStream in)
throws IOException
{
String status = in.readln();
if (status == null) {
throw new WebSocketProtocolException(L.l("Unexpected connection close", status));
}
if (! status.startsWith("HTTP")) {
throw new WebSocketProtocolException(L.l("Unexpected response {0}", status));
}
String line;
while ((line = in.readln()) != null && line.length() != 0) {
int p = line.indexOf(':');
if (p > 0) {
String header = line.substring(0, p).trim();
String value = line.substring(p + 1).trim();
}
}
if (! status.startsWith("HTTP/1.1 101")) {
StringBuilder sb = new StringBuilder();
int ch;
while (in.available() > 0 && (ch = in.read()) >= 0) {
sb.append((char) ch);
}
throw new WebSocketProtocolException(L.l("Unexpected response {0}\n\n{1}",
status, sb));
}
}
@Override
public void disconnect()
{
OutputStream os = _os;
try {
if (os != null)
os.close();
} catch (IOException e) {
log.log(Level.WARNING, e.toString(), e);
}
}
@Override
public BlockingQueue createOutputQueue(WebSocketEncoder encoder)
{
return new WebSocketBlockingQueue(this, encoder, 256);
}
@Override
public void setAutoFlush(boolean isAutoFlush)
{
}
@Override
public boolean isAutoFlush()
{
return false;
}
@Override
public void flush() throws IOException
{
}
public boolean isClosed()
{
return _isClosed;
}
@Override
public void close()
{
close(1000, "ok");
}
@Override
public void close(int code, String msg)
{
_isClosed = true;
OutputStream os = _os;
_os = null;
InputStream is = _is;
_is = null;
Socket s = _s;
_s = null;
try {
if (os != null)
os.close();
} catch (IOException e) {
log.log(Level.WARNING, e.toString(), e);
}
try {
if (is != null)
is.close();
} catch (IOException e) {
log.log(Level.WARNING, e.toString(), e);
}
try {
if (s != null)
s.close();
} catch (IOException e) {
log.log(Level.WARNING, e.toString(), e);
}
}
public InputStream getInputStream()
{
return _is;
}
@Override
public OutputStream startBinaryMessage()
throws IOException
{
if (_wsOs == null) {
OutputStream os = _os;
if (os == null)
throw new IllegalStateException(L.l("startBinaryMessage cannot be called with a closed context"));
_wsOs = new WebSocketOutputStream(os, new byte[4096]);
}
_wsOs.init();
return _wsOs;
}
@Override
public PrintWriter startTextMessage()
throws IOException
{
if (_wsWriter == null) {
OutputStream os = _os;
if (os == null)
throw new IllegalStateException(L.l("startTextMessage cannot be called with a closed context"));
_wsWriter = new WebSocketWriter(os, new byte[4096]);
}
_wsWriter.init();
return new WebSocketPrintWriter(_wsWriter);
}
@Override
public long getTimeout()
{
return 0;
}
@Override
public void setTimeout(long timeout)
{
}
@Override
public void ping(byte []message)
throws IOException
{
OutputStream out = _os;
if (out == null)
throw new IllegalStateException(L.l("ping cannot be called with a closed context"));
byte []bytes = message;
out.write(0x89);
out.write(bytes.length);
out.write(bytes);
out.flush();
}
@Override
public void pong(byte []message)
throws IOException
{
OutputStream out = _os;
if (out == null)
throw new IllegalStateException(L.l("pong cannot be called with a closed context"));
byte []bytes = message;
out.write(0x8a);
out.write(bytes.length);
out.write(bytes);
out.flush();
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _url + "]";
}
class ClientContext implements Runnable
{
@Override
public void run()
{
Thread thread = Thread.currentThread();
String name = thread.getName();
try {
thread.setName("web-socket-client");
handleRequests();
} catch (Exception e) {
if (_isClosed)
log.log(Level.FINEST, e.toString(), e);
else
log.log(Level.WARNING, e.toString(), e);
} finally {
try {
_listener.onDisconnect(WebSocketClient.this);
} catch (IOException e) {
log.log(Level.WARNING, e.toString(), e);
}
close(1000, "");
thread.setName(name);
}
}
public void handleRequests()
throws IOException
{
// server/2h20
// _listener.onStart(this);
FrameInputStream is = _frameIs;
while (is.readFrameHeader()) {
int op = is.getOpcode();
switch (op) {
case OP_BINARY:
if (_wsIs == null)
_wsIs = new WebSocketInputStream(is);
_wsIs.init();
try {
_listener.onReadBinary(WebSocketClient.this, _wsIs);
} finally {
_wsIs.close();
}
break;
default:
throw new IllegalStateException("Unknown WebSocket opcode 0x" +
Integer.toHexString(op));
}
}
}
}
@Override
public void onClose(int closeCode, String closeMessage)
{
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy