All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.websocket.client.io.UpgradeConnection Maven / Gradle / Ivy

//
//  ========================================================================
//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.websocket.client.io;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.UpgradeException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser.ParseException;
import org.eclipse.jetty.websocket.common.AcceptHash;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;

/**
 * This is the initial connection handling that exists immediately after physical connection is established to destination server.
 * 

* Eventually, upon successful Upgrade request/response, this connection swaps itself out for the WebSocektClientConnection handler. */ public class UpgradeConnection extends AbstractConnection { public class SendUpgradeRequest extends FutureCallback implements Runnable { @Override public void run() { URI uri = connectPromise.getRequest().getRequestURI(); request.setRequestURI(uri); String rawRequest = request.generate(); ByteBuffer buf = BufferUtil.toBuffer(rawRequest,StringUtil.__UTF8_CHARSET); getEndPoint().write(this,buf); } @Override public void succeeded() { // Writing the request header is complete. super.succeeded(); // start the interest in fill fillInterested(); } } /** HTTP Response Code: 101 Switching Protocols */ private static final int SWITCHING_PROTOCOLS = 101; private static final Logger LOG = Log.getLogger(UpgradeConnection.class); private final ByteBufferPool bufferPool; private final ConnectPromise connectPromise; private final HttpResponseHeaderParser parser; private ClientUpgradeRequest request; public UpgradeConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise) { super(endp,executor); this.connectPromise = connectPromise; this.bufferPool = connectPromise.getClient().getBufferPool(); this.request = connectPromise.getRequest(); // Setup the parser this.parser = new HttpResponseHeaderParser(); } public void disconnect(boolean onlyOutput) { EndPoint endPoint = getEndPoint(); // We need to gently close first, to allow // SSL close alerts to be sent by Jetty LOG.debug("Shutting down output {}",endPoint); endPoint.shutdownOutput(); if (!onlyOutput) { LOG.debug("Closing {}",endPoint); endPoint.close(); } } private void notifyConnect(ClientUpgradeResponse response) { connectPromise.setResponse(response); } @Override public void onFillable() { ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),true); BufferUtil.clear(buffer); boolean readMore = false; try { readMore = read(buffer); } finally { bufferPool.release(buffer); } if (readMore) { fillInterested(); } } @Override public void onOpen() { super.onOpen(); // TODO: handle timeout getExecutor().execute(new SendUpgradeRequest()); } /** * Read / Parse the waiting read/fill buffer * * @param buffer * the buffer to fill into from the endpoint * @return true if there is more to read, false if reading should stop */ private boolean read(ByteBuffer buffer) { EndPoint endPoint = getEndPoint(); try { while (true) { int filled = endPoint.fill(buffer); if (filled == 0) { return true; } else if (filled < 0) { LOG.debug("read - EOF Reached"); return false; } else { if (LOG.isDebugEnabled()) { LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer)); } ClientUpgradeResponse resp = parser.parse(buffer); if (resp != null) { // Got a response! validateResponse(resp); notifyConnect(resp); upgradeConnection(resp); if (buffer.hasRemaining()) { LOG.debug("Has remaining client bytebuffer of {}",buffer.remaining()); } return false; // do no more reading } } } } catch (IOException | ParseException e) { UpgradeException ue = new UpgradeException(request.getRequestURI(),e); connectPromise.failed(ue); disconnect(false); return false; } catch (UpgradeException e) { connectPromise.failed(e); disconnect(false); return false; } } private void upgradeConnection(ClientUpgradeResponse response) { EndPoint endp = getEndPoint(); Executor executor = getExecutor(); WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise); // Initialize / Negotiate Extensions EventDriver websocket = connectPromise.getDriver(); WebSocketPolicy policy = connectPromise.getClient().getPolicy(); WebSocketSession session = new WebSocketSession(request.getRequestURI(),websocket,connection); session.setPolicy(policy); session.setUpgradeResponse(response); connection.setSession(session); // Initialize / Negotiate Extensions ExtensionStack extensionStack = new ExtensionStack(connectPromise.getClient().getExtensionFactory()); extensionStack.negotiate(response.getExtensions()); extensionStack.configure(connection.getParser()); extensionStack.configure(connection.getGenerator()); // Setup Incoming Routing connection.setNextIncomingFrames(extensionStack); extensionStack.setNextIncoming(session); // Setup Outgoing Routing session.setOutgoingHandler(extensionStack); extensionStack.setNextOutgoing(connection); // Now swap out the connection endp.setConnection(connection); connection.onOpen(); } private void validateResponse(ClientUpgradeResponse response) { // Validate Response Status Code if (response.getStatusCode() != SWITCHING_PROTOCOLS) { throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Didn't switch protocols"); } // Validate Connection header String connection = response.getHeader("Connection"); if (!"upgrade".equalsIgnoreCase(connection)) { throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Connection is " + connection + " (expected upgrade)"); } // Check the Accept hash String reqKey = request.getKey(); String expectedHash = AcceptHash.hashKey(reqKey); String respHash = response.getHeader("Sec-WebSocket-Accept"); response.setSuccess(true); if (expectedHash.equalsIgnoreCase(respHash) == false) { response.setSuccess(false); throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Invalid Sec-WebSocket-Accept hash"); } // Parse extensions List extensions = new ArrayList<>(); List extValues = response.getHeaders("Sec-WebSocket-Extensions"); if (extValues != null) { for (String extVal : extValues) { QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,","); while (tok.hasMoreTokens()) { extensions.add(ExtensionConfig.parse(tok.nextToken())); } } } response.setExtensions(extensions); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy