Bug 391590 - WebSocket client needs ability to set requested extensions
* Adding UpgradeRequest.addExtensions(String ... extConfigs) interface * Implementing in WebSocket Client & WebSocket Server * Fixing case sensitive ClientUpgradeRequest.getHeaderValues(String key)
This commit is contained in:
parent
9b7eb1da79
commit
0cc7b5f907
|
@ -19,13 +19,19 @@
|
|||
package org.eclipse.jetty.websocket.client.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
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.core.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
|
||||
|
||||
|
@ -34,18 +40,58 @@ import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
|
|||
*/
|
||||
public class ClientUpgradeRequest implements UpgradeRequest
|
||||
{
|
||||
public static final String COOKIE_DELIM = "\"\\\n\r\t\f\b%+ ;=";
|
||||
private final static Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
|
||||
private static final String HEADER_VALUES_DELIM = "\"\\\n\r\t\f\b%+ ;=";
|
||||
private static final Set<String> FORBIDDEN_HEADERS;
|
||||
|
||||
static
|
||||
{
|
||||
// headers not allowed to be set in ClientUpgradeRequest.headers
|
||||
FORBIDDEN_HEADERS = new HashSet<>();
|
||||
FORBIDDEN_HEADERS.add("cookie");
|
||||
FORBIDDEN_HEADERS.add("upgrade");
|
||||
FORBIDDEN_HEADERS.add("host");
|
||||
FORBIDDEN_HEADERS.add("connection");
|
||||
FORBIDDEN_HEADERS.add("sec-websocket-key");
|
||||
FORBIDDEN_HEADERS.add("sec-websocket-extensions");
|
||||
FORBIDDEN_HEADERS.add("sec-websocket-accept");
|
||||
FORBIDDEN_HEADERS.add("sec-websocket-protocol");
|
||||
FORBIDDEN_HEADERS.add("sec-websocket-version");
|
||||
}
|
||||
|
||||
private final String key;
|
||||
private List<String> subProtocols;
|
||||
private List<ExtensionConfig> extensions;
|
||||
private Map<String, String> cookies;
|
||||
private Map<String, String> headers;
|
||||
private String httpEndPointName;
|
||||
private String host;
|
||||
|
||||
public ClientUpgradeRequest()
|
||||
{
|
||||
byte[] bytes = new byte[16];
|
||||
new Random().nextBytes(bytes);
|
||||
this.key = new String(B64Code.encode(bytes));
|
||||
this.subProtocols = new ArrayList<>();
|
||||
this.extensions = new ArrayList<>();
|
||||
this.cookies = new HashMap<>();
|
||||
this.headers = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(String... extConfigs)
|
||||
{
|
||||
for (String extConfig : extConfigs)
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(extConfig));
|
||||
}
|
||||
}
|
||||
|
||||
public String generate(URI uri)
|
||||
{
|
||||
this.httpEndPointName = uri.toASCIIString();
|
||||
this.host = uri.getHost();
|
||||
|
||||
StringBuilder request = new StringBuilder(512);
|
||||
request.append("GET ");
|
||||
if (StringUtil.isBlank(uri.getPath()))
|
||||
|
@ -62,36 +108,88 @@ public class ClientUpgradeRequest implements UpgradeRequest
|
|||
}
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
|
||||
request.append("Host: ").append(uri.getHost());
|
||||
request.append("Host: ").append(this.host);
|
||||
if (uri.getPort() > 0)
|
||||
{
|
||||
request.append(':').append(uri.getPort());
|
||||
}
|
||||
request.append("\r\n");
|
||||
|
||||
// WebSocket specifics
|
||||
request.append("Upgrade: websocket\r\n");
|
||||
request.append("Connection: Upgrade\r\n");
|
||||
request.append("Sec-WebSocket-Key: ").append(key).append("\r\n");
|
||||
|
||||
if (StringUtil.isNotBlank(getOrigin()))
|
||||
{
|
||||
request.append("Origin: ").append(getOrigin()).append("\r\n");
|
||||
}
|
||||
|
||||
request.append("Sec-WebSocket-Version: 13\r\n"); // RFC-6455 specified version
|
||||
|
||||
Map<String, String> cookies = getCookieMap();
|
||||
if ((cookies != null) && (cookies.size() > 0))
|
||||
// Extensions
|
||||
if (!getExtensions().isEmpty())
|
||||
{
|
||||
for (String cookie : cookies.keySet())
|
||||
request.append("Sec-WebSocket-Extensions: ");
|
||||
boolean needDelim = false;
|
||||
for (ExtensionConfig ext : getExtensions())
|
||||
{
|
||||
request.append("Cookie: ");
|
||||
request.append(QuotedStringTokenizer.quoteIfNeeded(cookie,COOKIE_DELIM));
|
||||
request.append("=");
|
||||
request.append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie),COOKIE_DELIM));
|
||||
request.append("\r\n");
|
||||
if (needDelim)
|
||||
{
|
||||
request.append(", ");
|
||||
}
|
||||
request.append(ext.getParameterizedName());
|
||||
needDelim = true;
|
||||
}
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// Sub Protocols
|
||||
if (!getSubProtocols().isEmpty())
|
||||
{
|
||||
request.append("Sec-WebSocket-Protocol: ");
|
||||
boolean needDelim = false;
|
||||
for (String protocol : getSubProtocols())
|
||||
{
|
||||
if (needDelim)
|
||||
{
|
||||
request.append(", ");
|
||||
}
|
||||
request.append(protocol);
|
||||
needDelim = true;
|
||||
}
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// Cookies
|
||||
if (!getCookieMap().isEmpty())
|
||||
{
|
||||
request.append("Cookie: ");
|
||||
boolean needDelim = false;
|
||||
for (String cookie : getCookieMap().keySet())
|
||||
{
|
||||
if (needDelim)
|
||||
{
|
||||
request.append("; ");
|
||||
}
|
||||
request.append(QuotedStringTokenizer.quoteIfNeeded(cookie,HEADER_VALUES_DELIM));
|
||||
request.append("=");
|
||||
String val = cookies.get(cookie);
|
||||
request.append(QuotedStringTokenizer.quoteIfNeeded(val,HEADER_VALUES_DELIM));
|
||||
needDelim = true;
|
||||
}
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// Other headers
|
||||
for (String key : headers.keySet())
|
||||
{
|
||||
String value = headers.get(key);
|
||||
if (FORBIDDEN_HEADERS.contains(key.toLowerCase()))
|
||||
{
|
||||
LOG.warn("Skipping forbidden header - {}: {}",key,value);
|
||||
continue; // skip
|
||||
}
|
||||
request.append(key).append(": ");
|
||||
request.append(QuotedStringTokenizer.quoteIfNeeded(value,HEADER_VALUES_DELIM));
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// request header end
|
||||
request.append("\r\n");
|
||||
return request.toString();
|
||||
}
|
||||
|
@ -99,36 +197,31 @@ public class ClientUpgradeRequest implements UpgradeRequest
|
|||
@Override
|
||||
public Map<String, String> getCookieMap()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return this.host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpEndPointName()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return httpEndPointName;
|
||||
}
|
||||
|
||||
public String getKey()
|
||||
|
@ -139,36 +232,45 @@ public class ClientUpgradeRequest implements UpgradeRequest
|
|||
@Override
|
||||
public String getOrigin()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return getHeader("Origin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubProtocols()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return subProtocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubProtocol(String test)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
for (String protocol : subProtocols)
|
||||
{
|
||||
if (protocol.equalsIgnoreCase(test))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOrigin(String test)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
return test.equalsIgnoreCase(getOrigin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubProtocols(String string)
|
||||
public void setSubProtocols(String protocols)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
this.subProtocols.clear();
|
||||
if (StringUtil.isBlank(protocols))
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (String protocol : protocols.split("\\s*,\\s*"))
|
||||
{
|
||||
this.subProtocols.add(protocol);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ public class ClientUpgradeResponse implements UpgradeResponse
|
|||
@Override
|
||||
public Iterator<String> getHeaderValues(String name)
|
||||
{
|
||||
List<String> values = headers.getValues(name);
|
||||
List<String> values = headers.getValues(name.toLowerCase());
|
||||
if (values == null)
|
||||
{
|
||||
return Collections.emptyIterator();
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.client.internal.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.internal.ClientUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.client.internal.IWebSocketClient;
|
||||
import org.eclipse.jetty.websocket.client.internal.DefaultWebSocketClient;
|
||||
import org.eclipse.jetty.websocket.core.api.Extension;
|
||||
import org.eclipse.jetty.websocket.core.api.UpgradeException;
|
||||
import org.eclipse.jetty.websocket.core.api.UpgradeResponse;
|
||||
|
@ -81,11 +81,11 @@ public class UpgradeConnection extends AbstractConnection
|
|||
|
||||
private static final Logger LOG = Log.getLogger(UpgradeConnection.class);
|
||||
private final ByteBufferPool bufferPool;
|
||||
private final IWebSocketClient client;
|
||||
private final DefaultWebSocketClient client;
|
||||
private final HttpResponseHeaderParser parser;
|
||||
private ClientUpgradeRequest request;
|
||||
|
||||
public UpgradeConnection(EndPoint endp, Executor executor, IWebSocketClient client)
|
||||
public UpgradeConnection(EndPoint endp, Executor executor, DefaultWebSocketClient client)
|
||||
{
|
||||
super(endp,executor);
|
||||
this.client = client;
|
||||
|
|
|
@ -70,11 +70,11 @@ public class SimpleEchoClient
|
|||
try
|
||||
{
|
||||
FutureCallback<Void> callback = new FutureCallback<>();
|
||||
conn.write(null,callback,"Echo Me!");
|
||||
conn.write(null,callback,"Hello");
|
||||
callback.get(2,TimeUnit.SECONDS); // wait for send to complete.
|
||||
|
||||
callback = new FutureCallback<>();
|
||||
conn.write(null,callback,"Echo Another One, please.");
|
||||
conn.write(null,callback,"Thanks for the conversation.");
|
||||
callback.get(2,TimeUnit.SECONDS); // wait for send to complete.
|
||||
|
||||
conn.close(StatusCode.NORMAL,"I'm done");
|
||||
|
@ -94,6 +94,11 @@ public class SimpleEchoClient
|
|||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String destUri = "ws://echo.websocket.org";
|
||||
if (args.length > 0)
|
||||
{
|
||||
destUri = args[0];
|
||||
}
|
||||
|
||||
WebSocketClientFactory factory = new WebSocketClientFactory();
|
||||
SimpleEchoSocket socket = new SimpleEchoSocket();
|
||||
|
@ -101,8 +106,9 @@ public class SimpleEchoClient
|
|||
{
|
||||
factory.start();
|
||||
WebSocketClient client = factory.newWebSocketClient(socket);
|
||||
URI echoUri = new URI("ws://echo.websocket.org");
|
||||
URI echoUri = new URI(destUri);
|
||||
System.out.printf("Connecting to : %s%n",echoUri);
|
||||
client.getUpgradeRequest().addExtensions("x-webkit-deflate-frame");
|
||||
client.connect(echoUri);
|
||||
|
||||
// wait for closed socket connection.
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
|
|||
|
||||
public interface UpgradeRequest
|
||||
{
|
||||
public void addExtensions(String... extConfigs);
|
||||
|
||||
public Map<String, String> getCookieMap();
|
||||
|
||||
public List<ExtensionConfig> getExtensions();
|
||||
|
@ -43,5 +45,5 @@ public interface UpgradeRequest
|
|||
|
||||
public boolean isOrigin(String test);
|
||||
|
||||
public void setSubProtocols(String string);
|
||||
public void setSubProtocols(String protocols);
|
||||
}
|
||||
|
|
|
@ -60,11 +60,20 @@ public class ServletWebSocketRequest extends HttpServletRequestWrapper implement
|
|||
QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),",");
|
||||
while (tok.hasMoreTokens())
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(tok.nextToken()));
|
||||
addExtensions(tok.nextToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(String... extConfigs)
|
||||
{
|
||||
for (String extConfig : extConfigs)
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(extConfig));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getCookieMap()
|
||||
{
|
||||
|
@ -102,13 +111,7 @@ public class ServletWebSocketRequest extends HttpServletRequestWrapper implement
|
|||
@Override
|
||||
public String getOrigin()
|
||||
{
|
||||
String origin = getHeader("Origin");
|
||||
if (origin == null)
|
||||
{
|
||||
// Fall back to older version
|
||||
origin = getHeader("Sec-WebSocket-Origin");
|
||||
}
|
||||
return origin;
|
||||
return getHeader("Origin");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -68,7 +68,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
|||
private final Map<Integer, WebSocketHandshake> handshakes = new HashMap<>();
|
||||
{
|
||||
handshakes.put(HandshakeRFC6455.VERSION,new HandshakeRFC6455());
|
||||
// OLD!! handshakes.put(HandshakeHixie76.VERSION,new HandshakeHixie76());
|
||||
}
|
||||
|
||||
private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
|
||||
|
|
Loading…
Reference in New Issue