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:
Joakim Erdfelt 2012-10-10 11:19:06 -07:00
parent 9b7eb1da79
commit 0cc7b5f907
7 changed files with 166 additions and 54 deletions

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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.

View File

@ -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);
}

View File

@ -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

View File

@ -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<>();