Java tutorial
/* * Copyright (c) 2002-2016 Gargoyle Software Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gargoylesoftware.htmlunit.javascript.host; import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.CHROME; import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.EDGE; import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.FF; import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.IE; import java.net.URI; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.client.WebSocketClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstant; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter; import com.gargoylesoftware.htmlunit.javascript.configuration.WebBrowser; import com.gargoylesoftware.htmlunit.javascript.host.event.EventTarget; import com.gargoylesoftware.htmlunit.javascript.host.event.MessageEvent; import net.sourceforge.htmlunit.corejs.javascript.Context; import net.sourceforge.htmlunit.corejs.javascript.Function; import net.sourceforge.htmlunit.corejs.javascript.Scriptable; /** * A JavaScript object for {@code WebSocket}. * * @author Ahmed Ashour * * @see <a href="https://developer.mozilla.org/en/WebSockets/WebSockets_reference/WebSocket">Mozilla documentation</a> */ @JsxClass(browsers = { @WebBrowser(CHROME), @WebBrowser(FF), @WebBrowser(IE), @WebBrowser(EDGE) }) public class WebSocket extends EventTarget implements AutoCloseable { private static final Log LOG = LogFactory.getLog(WebSocket.class); /** The connection has not yet been established. */ @JsxConstant public static final int CONNECTING = 0; /** The WebSocket connection is established and communication is possible. */ @JsxConstant public static final int OPEN = 1; /** The connection is going through the closing handshake. */ @JsxConstant public static final int CLOSING = 2; /** The connection has been closed or could not be opened. */ @JsxConstant public static final int CLOSED = 3; private Function closeHandler_; private Function errorHandler_; private Function messageHandler_; private Function openHandler_; private int readyState_ = CLOSED; private HtmlPage containingPage_; private WebSocketClient client_; private Session incomingSession_; private Session outgoingSession_; /** * Creates a new instance. */ public WebSocket() { } /** * Creates a new instance. * @param url the URL to which to connect * @param protocols if present, is either a string or an array of strings * @param window the top level window */ private WebSocket(final String url, final Object protocols, final Window window) { try { containingPage_ = (HtmlPage) window.getWebWindow().getEnclosedPage(); if (containingPage_.getWebClient().getOptions().isUseInsecureSSL()) { client_ = new WebSocketClient(new SslContextFactory(true)); } else { client_ = new WebSocketClient(); } client_.setCookieStore(new WebSocketCookieStore(window.getWebWindow().getWebClient())); client_.start(); incomingSession_ = client_.connect(new WebSocketImpl(), new URI(url)).get(); readyState_ = OPEN; containingPage_.addAutoCloseable(this); } catch (final Exception e) { LOG.error(e); } } /** * JavaScript constructor. * @param cx the current context * @param args the arguments to the WebSocket constructor * @param ctorObj the function object * @param inNewExpr Is new or not * @return the java object to allow JavaScript to access */ @JsxConstructor public static Scriptable jsConstructor(final Context cx, final Object[] args, final Function ctorObj, final boolean inNewExpr) { if (args.length < 1 || args.length > 2) { throw Context .reportRuntimeError("WebSocket Error: constructor must have one or two String parameters."); } if (args[0] == Context.getUndefinedValue()) { throw Context.reportRuntimeError("WebSocket Error: 'url' parameter is undefined."); } if (!(args[0] instanceof String)) { throw Context.reportRuntimeError("WebSocket Error: 'url' parameter must be a String."); } return new WebSocket((String) args[0], null, getWindow(ctorObj)); } /** * Returns the event handler that fires on close. * @return the event handler that fires on close */ @JsxGetter public Function getOnclose() { return closeHandler_; } /** * Sets the event handler that fires on close. * @param closeHandler the event handler that fires on close */ @JsxSetter public void setOnclose(final Function closeHandler) { closeHandler_ = closeHandler; } /** * Returns the event handler that fires on error. * @return the event handler that fires on error */ @JsxGetter public Function getOnerror() { return errorHandler_; } /** * Sets the event handler that fires on error. * @param errorHandler the event handler that fires on error */ @JsxSetter public void setOnerror(final Function errorHandler) { errorHandler_ = errorHandler; } /** * Returns the event handler that fires on message. * @return the event handler that fires on message */ @JsxGetter public Function getOnmessage() { return messageHandler_; } /** * Sets the event handler that fires on message. * @param messageHandler the event handler that fires on message */ @JsxSetter public void setOnmessage(final Function messageHandler) { messageHandler_ = messageHandler; } /** * Returns the event handler that fires on open. * @return the event handler that fires on open */ @JsxGetter public Function getOnopen() { return openHandler_; } /** * Sets the event handler that fires on open. * @param openHandler the event handler that fires on open */ @JsxSetter public void setOnopen(final Function openHandler) { openHandler_ = openHandler; fireOnOpen(); } /** * Returns The current state of the connection. The possible values are: {@link #CONNECTING}, {@link #OPEN}, * {@link #CLOSING} or {@link #CLOSED}. * @return the current state of the connection */ @JsxGetter public int getReadyState() { return readyState_; } /** * {@inheritDoc} */ @Override public void close() throws Exception { close(null, null); } /** * Closes the WebSocket connection or connection attempt, if any. * If the connection is already {@link #CLOSED}, this method does nothing. * @param code A numeric value indicating the status code explaining why the connection is being closed * @param reason A human-readable string explaining why the connection is closing */ @JsxFunction public void close(final Object code, final Object reason) { if (readyState_ != CLOSED) { if (incomingSession_ != null) { incomingSession_.close(); } if (outgoingSession_ != null) { outgoingSession_.close(); } try { client_.stop(); } catch (final Exception e) { throw new RuntimeException(e); } readyState_ = CLOSED; } } /** * Transmits data to the server over the WebSocket connection. * @param content the body of the message being sent with the request */ @JsxFunction public void send(final Object content) { try { if (content instanceof String) { outgoingSession_.getRemote().sendString(content.toString()); } else { throw new IllegalStateException( "Not Yet Implemented: WebSocket.send() was used to send non-string value"); } } catch (final Exception e) { LOG.error(e); } } private void fireOnOpen() { if (openHandler_ == null) { return; } final Scriptable scope = openHandler_.getParentScope(); final JavaScriptEngine jsEngine = containingPage_.getWebClient().getJavaScriptEngine(); jsEngine.callFunction(containingPage_, openHandler_, scope, WebSocket.this, ArrayUtils.EMPTY_OBJECT_ARRAY); } private class WebSocketImpl extends WebSocketAdapter { @Override public void onWebSocketConnect(final Session session) { outgoingSession_ = session; } @Override public void onWebSocketClose(final int closeCode, final String message) { if (closeHandler_ == null) { return; } final Scriptable scope = closeHandler_.getParentScope(); final JavaScriptEngine jsEngine = containingPage_.getWebClient().getJavaScriptEngine(); jsEngine.callFunction(containingPage_, closeHandler_, scope, WebSocket.this, new Object[] { closeCode, message }); } @Override public void onWebSocketText(final String data) { if (messageHandler_ == null) { return; } final Scriptable scope = messageHandler_.getParentScope(); final JavaScriptEngine jsEngine = containingPage_.getWebClient().getJavaScriptEngine(); final MessageEvent event = new MessageEvent(data); event.setParentScope(getParentScope()); event.setPrototype(getPrototype(event.getClass())); jsEngine.callFunction(containingPage_, messageHandler_, scope, WebSocket.this, new Object[] { event }); } @Override public void onWebSocketBinary(final byte[] data, final int offset, final int length) { if (messageHandler_ == null) { return; } final Scriptable scope = messageHandler_.getParentScope(); final JavaScriptEngine jsEngine = containingPage_.getWebClient().getJavaScriptEngine(); jsEngine.callFunction(containingPage_, messageHandler_, scope, WebSocket.this, new Object[] { data, offset, length }); } } }