org.eclipse.jetty.websocket.client.ClientUpgradeRequest Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.client;
import java.net.HttpCookie;
import java.net.URI;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.websocket.api.ExtensionConfig;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
/**
* Client based UpgradeRequest API
*/
public final class ClientUpgradeRequest implements UpgradeRequest
{
private final List subProtocols = new ArrayList<>(1);
private final List extensions = new ArrayList<>(1);
private final List cookies = new ArrayList<>(1);
private final Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final URI requestURI;
private final String host;
private long timeout;
public ClientUpgradeRequest()
{
/* anonymous, no requestURI, upgrade request */
this.requestURI = null;
this.host = null;
}
/**
* @deprecated use {@link #ClientUpgradeRequest()} instead.
*/
@Deprecated
public ClientUpgradeRequest(URI uri)
{
this.requestURI = uri;
String scheme = uri.getScheme();
if (!HttpScheme.WS.is(scheme) && !HttpScheme.WSS.is(scheme))
throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
this.host = this.requestURI.getHost();
}
@Override
public List getCookies()
{
return cookies;
}
@Override
public List getExtensions()
{
return extensions;
}
@Override
public String getHeader(String name)
{
List values = headers.get(name);
return joinValues(values);
}
@Override
public int getHeaderInt(String name)
{
List values = headers.get(name);
// no value list
if (values == null)
{
return -1;
}
int size = values.size();
// empty value list
if (size <= 0)
{
return -1;
}
// simple return
if (size == 1)
{
return Integer.parseInt(values.get(0));
}
throw new NumberFormatException("Cannot convert multi-value header into int");
}
@Override
public Map> getHeaders()
{
return headers;
}
@Override
public List getHeaders(String name)
{
return headers.get(name);
}
@Override
public String getHost()
{
return host;
}
@Override
public String getHttpVersion()
{
throw new UnsupportedOperationException("HttpVersion not available on ClientUpgradeRequest");
}
@Override
public String getMethod()
{
throw new UnsupportedOperationException("Method not available on ClientUpgradeRequest");
}
@Override
public String getOrigin()
{
return getHeader(HttpHeader.ORIGIN.name());
}
@Override
public Map> getParameterMap()
{
return Collections.emptyMap();
}
@Override
public String getProtocolVersion()
{
String version = getHeader("Sec-WebSocket-Version");
return Objects.requireNonNullElse(version, "13");
}
@Override
public String getQueryString()
{
return requestURI == null ? null : requestURI.getQuery();
}
@Override
public URI getRequestURI()
{
return requestURI;
}
@Override
public List getSubProtocols()
{
return subProtocols;
}
@Override
public Principal getUserPrincipal()
{
throw new UnsupportedOperationException("User Principal not available on Client request");
}
@Override
public boolean hasSubProtocol(String test)
{
for (String protocol : subProtocols)
{
if (protocol.equalsIgnoreCase(test))
{
return true;
}
}
return false;
}
@Override
public boolean isSecure()
{
throw new UnsupportedOperationException("Request.isSecure not available on Client request");
}
/**
* Add WebSocket Extension Configuration(s) to Upgrade Request.
*
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
* negotiated
*
* @param configs the configuration(s) to add
*/
public void addExtensions(ExtensionConfig... configs)
{
Collections.addAll(extensions, configs);
}
/**
* Add WebSocket Extension Configuration(s) to request
*
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
* negotiated
*
* @param configs the configuration(s) to add
*/
public void addExtensions(String... configs)
{
for (String config : configs)
{
extensions.add(ExtensionConfig.parse(config));
}
}
/**
* Set the list of Cookies on the request
*
* @param cookies the cookies to use
*/
public void setCookies(List cookies)
{
this.cookies.clear();
if (cookies != null && !cookies.isEmpty())
{
this.cookies.addAll(cookies);
}
}
/**
* Set the list of WebSocket Extension configurations on the request.
*
* @param configs the list of extension configurations
*/
public void setExtensions(List configs)
{
this.extensions.clear();
if (configs != null)
{
this.extensions.addAll(configs);
}
}
/**
* Set a specific header with multi-value field
*
* Overrides any previous value for this named header
*
* @param name the name of the header
* @param values the multi-value field
*/
public void setHeader(String name, List values)
{
headers.put(name, values);
}
/**
* Set a specific header value
*
* Overrides any previous value for this named header
*
* @param name the header to set
* @param value the value to set it to
*/
public void setHeader(String name, String value)
{
List values = new ArrayList<>();
values.add(value);
setHeader(name, values);
}
/**
* Sets multiple headers on the request.
*
* Only sets those headers provided, does not remove
* headers that exist on request and are not provided in the
* parameter for this method.
*
* Convenience method vs calling {@link #setHeader(String, List)} multiple times.
*
* @param headers the headers to set
*/
public void setHeaders(Map> headers)
{
this.headers.clear();
for (Map.Entry> entry : headers.entrySet())
{
String name = entry.getKey();
List values = entry.getValue();
setHeader(name, values);
}
}
/**
* Set the offered WebSocket Sub-Protocol list.
*
* @param protocols the offered sub-protocol list
*/
public void setSubProtocols(List protocols)
{
this.subProtocols.clear();
if (protocols != null)
{
this.subProtocols.addAll(protocols);
}
}
/**
* Set the offered WebSocket Sub-Protocol list.
*
* @param protocols the offered sub-protocol list
*/
public void setSubProtocols(String... protocols)
{
subProtocols.clear();
Collections.addAll(subProtocols, protocols);
}
/**
* @param timeout the total timeout for the request/response conversation of the WebSocket handshake;
* use zero or a negative value to disable the timeout
* @param unit the timeout unit
*/
public void setTimeout(long timeout, TimeUnit unit)
{
this.timeout = unit.toMillis(timeout);
}
/**
* @return the total timeout for this request, in milliseconds;
* zero or negative if the timeout is disabled
*/
public long getTimeout()
{
return timeout;
}
/**
* ABNF from RFC 2616, RFC 822, and RFC 6455 specified characters requiring quoting.
*/
public static final String ABNF_REQUIRED_QUOTING = "\"'\\\n\r\t\f\b%+ ;=";
public static String joinValues(List values)
{
// no value list
if (values == null)
{
return null;
}
int size = values.size();
// empty value list
if (size <= 0)
{
return null;
}
// simple return
if (size == 1)
{
return values.get(0);
}
// join it with commas
boolean needsDelim = false;
StringBuilder ret = new StringBuilder();
for (String value : values)
{
if (needsDelim)
{
ret.append(", ");
}
quoteIfNeeded(ret, value, ABNF_REQUIRED_QUOTING);
needsDelim = true;
}
return ret.toString();
}
/**
* Append into buf the provided string, adding quotes if needed.
*
* Quoting is determined if any of the characters in the {@code delim} are found in the input {@code str}.
*
* @param buf the buffer to append to
* @param str the string to possibly quote
* @param delim the delimiter characters that will trigger automatic quoting
*/
private static void quoteIfNeeded(StringBuilder buf, String str, String delim)
{
if (str == null)
{
return;
}
// check for delimiters in input string
int len = str.length();
if (len == 0)
{
return;
}
int ch;
for (int i = 0; i < len; i++)
{
ch = str.codePointAt(i);
if (delim.indexOf(ch) >= 0)
{
// found a delimiter codepoint. we need to quote it.
buf.append('"');
buf.append(str);
buf.append('"');
return;
}
}
// no special delimiters used, no quote needed.
buf.append(str);
}
}