From 8fcbf6d590aabf681efbad7d3d5bf3bb83462503 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 28 Apr 2020 10:16:29 +0200 Subject: [PATCH] Improve Attributes Handling (#4816) * Spun out from #4814 Improve Attributes Handling Improve attribute handling to reduce garbage and improve lookup. Introduced a Wrapper so that request can remove any layers on reset. Signed-off-by: Greg Wilkins * Issue #4814 - Exposing AttributeMap.getAttributeNameSet() on Attributes. The underlying AttributesMap already has a .getAttributeNameSet() method, expose it on the Attributes interface. Signed-off-by: Joakim Erdfelt * Allow a set to override a secure attribute. Signed-off-by: Greg Wilkins * Issue #4814 - Attributes.getAttributeNames() is now defaulted The Attributes.getAttributeNames() will use the .getAttributeNameSet() by default now. Updated all Attributes.Wrapper impls to use this new behavior Signed-off-by: Joakim Erdfelt Co-authored-by: Joakim Erdfelt --- .../org/eclipse/jetty/server/Dispatcher.java | 172 ++++++++++-------- .../org/eclipse/jetty/server/Request.java | 8 +- .../jetty/server/SecureRequestCustomizer.java | 132 +++++++------- .../java/org/eclipse/jetty/server/Server.java | 7 + .../jetty/server/handler/ContextHandler.java | 6 + .../handler/ManagedAttributeListener.java | 9 +- .../handler/jmx/ContextHandlerMBean.java | 5 +- .../org/eclipse/jetty/util/Attributes.java | 63 ++++++- .../org/eclipse/jetty/util/AttributesMap.java | 1 + .../util/component/AttributeContainerMap.java | 7 + 10 files changed, 250 insertions(+), 160 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java index baa596aedf15..1f3aa6150c5e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -19,9 +19,8 @@ package org.eclipse.jetty.server; import java.io.IOException; -import java.util.Collections; -import java.util.Enumeration; import java.util.HashSet; +import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; @@ -252,10 +251,8 @@ public String toString() return String.format("Dispatcher@0x%x{%s,%s}", hashCode(), _named, _uri); } - private class ForwardAttributes implements Attributes + private class ForwardAttributes extends Attributes.Wrapper { - final Attributes _attr; - String _requestURI; String _contextPath; String _servletPath; @@ -264,7 +261,7 @@ private class ForwardAttributes implements Attributes ForwardAttributes(Attributes attributes) { - _attr = attributes; + super(attributes); } @Override @@ -272,32 +269,35 @@ public Object getAttribute(String key) { if (Dispatcher.this._named == null) { - if (key.equals(FORWARD_PATH_INFO)) - return _pathInfo; - if (key.equals(FORWARD_REQUEST_URI)) - return _requestURI; - if (key.equals(FORWARD_SERVLET_PATH)) - return _servletPath; - if (key.equals(FORWARD_CONTEXT_PATH)) - return _contextPath; - if (key.equals(FORWARD_QUERY_STRING)) - return _query; + switch (key) + { + case FORWARD_PATH_INFO: + return _pathInfo; + case FORWARD_REQUEST_URI: + return _requestURI; + case FORWARD_SERVLET_PATH: + return _servletPath; + case FORWARD_CONTEXT_PATH: + return _contextPath; + case FORWARD_QUERY_STRING: + return _query; + default: + break; + } } if (key.startsWith(__INCLUDE_PREFIX)) return null; - return _attr.getAttribute(key); + return _attributes.getAttribute(key); } @Override - public Enumeration getAttributeNames() + public Set getAttributeNameSet() { HashSet set = new HashSet<>(); - Enumeration e = _attr.getAttributeNames(); - while (e.hasMoreElements()) + for (String name : _attributes.getAttributeNameSet()) { - String name = e.nextElement(); if (!name.startsWith(__INCLUDE_PREFIX) && !name.startsWith(__FORWARD_PREFIX)) set.add(name); @@ -318,7 +318,7 @@ public Enumeration getAttributeNames() set.remove(FORWARD_QUERY_STRING); } - return Collections.enumeration(set); + return set; } @Override @@ -326,32 +326,40 @@ public void setAttribute(String key, Object value) { if (_named == null && key.startsWith("javax.servlet.")) { - if (key.equals(FORWARD_PATH_INFO)) - _pathInfo = (String)value; - else if (key.equals(FORWARD_REQUEST_URI)) - _requestURI = (String)value; - else if (key.equals(FORWARD_SERVLET_PATH)) - _servletPath = (String)value; - else if (key.equals(FORWARD_CONTEXT_PATH)) - _contextPath = (String)value; - else if (key.equals(FORWARD_QUERY_STRING)) - _query = (String)value; - - else if (value == null) - _attr.removeAttribute(key); - else - _attr.setAttribute(key, value); + switch (key) + { + case FORWARD_PATH_INFO: + _pathInfo = (String)value; + return; + case FORWARD_REQUEST_URI: + _requestURI = (String)value; + return; + case FORWARD_SERVLET_PATH: + _servletPath = (String)value; + return; + case FORWARD_CONTEXT_PATH: + _contextPath = (String)value; + return; + case FORWARD_QUERY_STRING: + _query = (String)value; + return; + default: + if (value == null) + _attributes.removeAttribute(key); + else + _attributes.setAttribute(key, value); + } } else if (value == null) - _attr.removeAttribute(key); + _attributes.removeAttribute(key); else - _attr.setAttribute(key, value); + _attributes.setAttribute(key, value); } @Override public String toString() { - return "FORWARD+" + _attr.toString(); + return "FORWARD+" + _attributes.toString(); } @Override @@ -367,10 +375,8 @@ public void removeAttribute(String name) } } - private class IncludeAttributes implements Attributes + private class IncludeAttributes extends Attributes.Wrapper { - final Attributes _attr; - String _requestURI; String _contextPath; String _servletPath; @@ -379,7 +385,7 @@ private class IncludeAttributes implements Attributes IncludeAttributes(Attributes attributes) { - _attr = attributes; + super(attributes); } @Override @@ -387,31 +393,34 @@ public Object getAttribute(String key) { if (Dispatcher.this._named == null) { - if (key.equals(INCLUDE_PATH_INFO)) - return _pathInfo; - if (key.equals(INCLUDE_SERVLET_PATH)) - return _servletPath; - if (key.equals(INCLUDE_CONTEXT_PATH)) - return _contextPath; - if (key.equals(INCLUDE_QUERY_STRING)) - return _query; - if (key.equals(INCLUDE_REQUEST_URI)) - return _requestURI; + switch (key) + { + case INCLUDE_PATH_INFO: + return _pathInfo; + case INCLUDE_SERVLET_PATH: + return _servletPath; + case INCLUDE_CONTEXT_PATH: + return _contextPath; + case INCLUDE_QUERY_STRING: + return _query; + case INCLUDE_REQUEST_URI: + return _requestURI; + default: + break; + } } else if (key.startsWith(__INCLUDE_PREFIX)) return null; - return _attr.getAttribute(key); + return _attributes.getAttribute(key); } @Override - public Enumeration getAttributeNames() + public Set getAttributeNameSet() { HashSet set = new HashSet<>(); - Enumeration e = _attr.getAttributeNames(); - while (e.hasMoreElements()) + for (String name : _attributes.getAttributeNameSet()) { - String name = e.nextElement(); if (!name.startsWith(__INCLUDE_PREFIX)) set.add(name); } @@ -431,7 +440,7 @@ public Enumeration getAttributeNames() set.remove(INCLUDE_QUERY_STRING); } - return Collections.enumeration(set); + return set; } @Override @@ -439,31 +448,40 @@ public void setAttribute(String key, Object value) { if (_named == null && key.startsWith("javax.servlet.")) { - if (key.equals(INCLUDE_PATH_INFO)) - _pathInfo = (String)value; - else if (key.equals(INCLUDE_REQUEST_URI)) - _requestURI = (String)value; - else if (key.equals(INCLUDE_SERVLET_PATH)) - _servletPath = (String)value; - else if (key.equals(INCLUDE_CONTEXT_PATH)) - _contextPath = (String)value; - else if (key.equals(INCLUDE_QUERY_STRING)) - _query = (String)value; - else if (value == null) - _attr.removeAttribute(key); - else - _attr.setAttribute(key, value); + switch (key) + { + case INCLUDE_PATH_INFO: + _pathInfo = (String)value; + return; + case INCLUDE_REQUEST_URI: + _requestURI = (String)value; + return; + case INCLUDE_SERVLET_PATH: + _servletPath = (String)value; + return; + case INCLUDE_CONTEXT_PATH: + _contextPath = (String)value; + return; + case INCLUDE_QUERY_STRING: + _query = (String)value; + return; + default: + if (value == null) + _attributes.removeAttribute(key); + else + _attributes.setAttribute(key, value); + } } else if (value == null) - _attr.removeAttribute(key); + _attributes.removeAttribute(key); else - _attr.setAttribute(key, value); + _attributes.setAttribute(key, value); } @Override public String toString() { - return "INCLUDE+" + _attr.toString(); + return "INCLUDE+" + _attributes.toString(); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index ac93f3c6d5fb..8b0bf63e9741 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -1867,8 +1867,14 @@ protected void recycle() _async = null; _asyncNotSupportedSource = null; _handled = false; + _attributes = Attributes.unwrap(_attributes); if (_attributes != null) - _attributes.clearAttributes(); + { + if (AttributesMap.class.equals(_attributes.getClass())) + _attributes.clearAttributes(); + else + _attributes = null; + } _contentType = null; _characterEncoding = null; _contextPath = null; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java index 7d8e8a8fca2f..60d0ef72c248 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java @@ -19,6 +19,8 @@ package org.eclipse.jetty.server; import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -33,6 +35,8 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.log.Log; @@ -49,11 +53,10 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer { private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class); - - /** - * The name of the SSLSession attribute that will contain any cached information. - */ - public static final String CACHED_INFO_ATTR = CachedInfo.class.getName(); + public static final String JAVAX_SERVLET_REQUEST_X_509_CERTIFICATE = "javax.servlet.request.X509Certificate"; + public static final String JAVAX_SERVLET_REQUEST_CIPHER_SUITE = "javax.servlet.request.cipher_suite"; + public static final String JAVAX_SERVLET_REQUEST_KEY_SIZE = "javax.servlet.request.key_size"; + public static final String JAVAX_SERVLET_REQUEST_SSL_SESSION_ID = "javax.servlet.request.ssl_session_id"; private String sslSessionAttribute = "org.eclipse.jetty.servlet.request.ssl_session"; @@ -262,61 +265,22 @@ protected void customize(SSLEngine sslEngine, Request request) if (_sniHostCheck || _sniRequired) { - String name = request.getServerName(); X509 x509 = (X509)sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509); - if (LOG.isDebugEnabled()) - LOG.debug("Host {} with SNI {}", name, x509); + LOG.debug("Host {} with SNI {}", request.getServerName(), x509); if (x509 == null) { if (_sniRequired) throw new BadMessageException(400, "SNI required"); } - else if (_sniHostCheck && !x509.matches(name)) + else if (_sniHostCheck && !x509.matches(request.getServerName())) { throw new BadMessageException(400, "Host does not match SNI"); } } - try - { - String cipherSuite = sslSession.getCipherSuite(); - Integer keySize; - X509Certificate[] certs; - String idStr; - - CachedInfo cachedInfo = (CachedInfo)sslSession.getValue(CACHED_INFO_ATTR); - if (cachedInfo != null) - { - keySize = cachedInfo.getKeySize(); - certs = cachedInfo.getCerts(); - idStr = cachedInfo.getIdStr(); - } - else - { - keySize = SslContextFactory.deduceKeyLength(cipherSuite); - certs = getCertChain(request, sslSession); - byte[] bytes = sslSession.getId(); - idStr = TypeUtil.toHexString(bytes); - cachedInfo = new CachedInfo(keySize, certs, idStr); - sslSession.putValue(CACHED_INFO_ATTR, cachedInfo); - } - - if (certs != null) - request.setAttribute("javax.servlet.request.X509Certificate", certs); - - request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite); - request.setAttribute("javax.servlet.request.key_size", keySize); - request.setAttribute("javax.servlet.request.ssl_session_id", idStr); - String sessionAttribute = getSslSessionAttribute(); - if (sessionAttribute != null && !sessionAttribute.isEmpty()) - request.setAttribute(sessionAttribute, sslSession); - } - catch (Exception e) - { - LOG.warn(Log.EXCEPTION, e); - } + request.setAttributes(new SslAttributes(request, sslSession, request.getAttributes())); } private X509Certificate[] getCertChain(Request request, SSLSession sslSession) @@ -327,10 +291,8 @@ private X509Certificate[] getCertChain(Request request, SSLSession sslSession) if (sslConnectionFactory != null) { SslContextFactory sslContextFactory = sslConnectionFactory.getSslContextFactory(); - if (sslConnectionFactory != null) - { + if (sslContextFactory != null) return sslContextFactory.getX509CertChain(sslSession); - } } // Fallback, either no SslConnectionFactory or no SslContextFactory instance found @@ -353,36 +315,66 @@ public String toString() return String.format("%s@%x", this.getClass().getSimpleName(), hashCode()); } - /** - * Simple bundle of information that is cached in the SSLSession. Stores the - * effective keySize and the client certificate chain. - */ - private static class CachedInfo + private class SslAttributes extends Attributes.Wrapper { - private final X509Certificate[] _certs; - private final Integer _keySize; - private final String _idStr; + private final Request _request; + private final SSLSession _session; - CachedInfo(Integer keySize, X509Certificate[] certs, String idStr) + public SslAttributes(Request request, SSLSession sslSession, Attributes attributes) { - this._keySize = keySize; - this._certs = certs; - this._idStr = idStr; + super(attributes); + this._request = request; + this._session = sslSession; } - X509Certificate[] getCerts() + @Override + public Object getAttribute(String name) { - return _certs; - } + Object value = _attributes.getAttribute(name); + if (value != null) + return value; + try + { + switch (name) + { + case JAVAX_SERVLET_REQUEST_X_509_CERTIFICATE: + return SecureRequestCustomizer.this.getCertChain(_request, _session); - Integer getKeySize() - { - return _keySize; + case JAVAX_SERVLET_REQUEST_CIPHER_SUITE: + return _session.getCipherSuite(); + + case JAVAX_SERVLET_REQUEST_KEY_SIZE: + return SslContextFactory.deduceKeyLength(_session.getCipherSuite()); + + case JAVAX_SERVLET_REQUEST_SSL_SESSION_ID: + return TypeUtil.toHexString(_session.getId()); + + default: + String sessionAttribute = getSslSessionAttribute(); + if (!StringUtil.isEmpty(sessionAttribute) && sessionAttribute.equals(name)) + return _session; + } + } + catch (Exception e) + { + if (LOG.isDebugEnabled()) + LOG.debug("Unable to get secure details ", e); + } + return null; } - String getIdStr() + @Override + public Set getAttributeNameSet() { - return _idStr; + Set names = new HashSet<>(_attributes.getAttributeNameSet()); + names.add(JAVAX_SERVLET_REQUEST_X_509_CERTIFICATE); + names.add(JAVAX_SERVLET_REQUEST_CIPHER_SUITE); + names.add(JAVAX_SERVLET_REQUEST_KEY_SIZE); + names.add(JAVAX_SERVLET_REQUEST_SSL_SESSION_ID); + String sessionAttribute = getSslSessionAttribute(); + if (!StringUtil.isEmpty(sessionAttribute)) + names.add(sessionAttribute); + return names; } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index d23416044c57..af8bcf1db36e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Enumeration; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import javax.servlet.ServletContext; @@ -594,6 +595,12 @@ public Enumeration getAttributeNames() return _attributes.getAttributeNames(); } + @Override + public Set getAttributeNameSet() + { + return _attributes.getAttributeNameSet(); + } + /* * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String) */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 725aeb5f40d3..d89a3b861f2f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -516,6 +516,12 @@ public Enumeration getAttributeNames() return AttributesMap.getAttributeNamesCopy(_attributes); } + @Override + public Set getAttributeNameSet() + { + return _attributes.getAttributeNameSet(); + } + /** * @return Returns the attributes. */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java index fdca2fdab8e9..225e0f1eaf5c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.server.handler; -import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import javax.servlet.ServletContextAttributeEvent; @@ -77,10 +76,8 @@ public void attributeAdded(ServletContextAttributeEvent event) public void contextInitialized(ServletContextEvent event) { // Update existing attributes - Enumeration e = event.getServletContext().getAttributeNames(); - while (e.hasMoreElements()) + for (String name : _context.getServletContext().getAttributeNameSet()) { - String name = e.nextElement(); if (_managedAttributes.contains(name)) updateBean(name, null, event.getServletContext().getAttribute(name)); } @@ -89,10 +86,8 @@ public void contextInitialized(ServletContextEvent event) @Override public void contextDestroyed(ServletContextEvent event) { - Enumeration e = _context.getServletContext().getAttributeNames(); - while (e.hasMoreElements()) + for (String name : _context.getServletContext().getAttributeNameSet()) { - String name = e.nextElement(); if (_managedAttributes.contains(name)) updateBean(name, event.getServletContext().getAttribute(name), null); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java index 1c73c8f11f97..b102b8769d0e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.server.handler.jmx; -import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @@ -42,10 +41,8 @@ public Map getContextAttributes() { Map map = new HashMap(); Attributes attrs = ((ContextHandler)_managed).getAttributes(); - Enumeration en = attrs.getAttributeNames(); - while (en.hasMoreElements()) + for (String name : attrs.getAttributeNameSet()) { - String name = en.nextElement(); Object value = attrs.getAttribute(name); map.put(name, value); } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java index cc4818f68349..e276e27dfc53 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java @@ -18,7 +18,9 @@ package org.eclipse.jetty.util; +import java.util.Collections; import java.util.Enumeration; +import java.util.Set; /** * Attributes. @@ -32,7 +34,66 @@ public interface Attributes Object getAttribute(String name); - Enumeration getAttributeNames(); + Set getAttributeNameSet(); + + default Enumeration getAttributeNames() + { + return Collections.enumeration(getAttributeNameSet()); + } void clearAttributes(); + + static Attributes unwrap(Attributes attributes) + { + while (attributes instanceof Wrapper) + { + attributes = ((Wrapper)attributes).getAttributes(); + } + return attributes; + } + + abstract class Wrapper implements Attributes + { + protected final Attributes _attributes; + + public Wrapper(Attributes attributes) + { + _attributes = attributes; + } + + public Attributes getAttributes() + { + return _attributes; + } + + @Override + public void removeAttribute(String name) + { + _attributes.removeAttribute(name); + } + + @Override + public void setAttribute(String name, Object attribute) + { + _attributes.setAttribute(name, attribute); + } + + @Override + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + @Override + public Set getAttributeNameSet() + { + return _attributes.getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + _attributes.clearAttributes(); + } + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java index 5ea9ac45823a..8c41e073ce16 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java @@ -94,6 +94,7 @@ public Enumeration getAttributeNames() return Collections.enumeration(getAttributeNameSet()); } + @Override public Set getAttributeNameSet() { return keySet(); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AttributeContainerMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AttributeContainerMap.java index abbd1543b08a..bfde2301bfd5 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AttributeContainerMap.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AttributeContainerMap.java @@ -23,6 +23,7 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.eclipse.jetty.util.Attributes; @@ -60,6 +61,12 @@ public synchronized Enumeration getAttributeNames() return Collections.enumeration(_map.keySet()); } + @Override + public Set getAttributeNameSet() + { + return _map.keySet(); + } + @Override public synchronized void clearAttributes() {